此應用程式以 墨水範例收藏 範例為基礎,展示了如何刪除筆畫。 此範例會為使用者提供一個功能表,其中包含四種模式可供選擇:啟用墨跡、在尖點處清除、在交叉點清除,以及清除筆劃。
在啟用筆跡模式的模式中,InkCollector 物件會收集筆跡,如 Ink 集合範例所示。
在清除模式中,會清除使用者用游標觸控的現有筆墨筆劃區段。 此外,尖點或交集可能會以紅色圓圈標示。
這個範例最有趣的部分在於 InkErase 表單的 OnPaint 事件處理程式,以及從表單的 OnMouseMove 事件處理程式呼叫的清除函式。
環繞尖點和交點
表單的 OnPaint 事件處理程式會先繪製筆劃,並視應用模式而定,可能會找到所有尖頂或交點並用小紅色圓圈標記。 尖點標示筆劃在突然變更方向的地方。 交叉點是指一個筆劃與自身或其他筆劃相交的地方。
每當重新繪製控件時,就會發生 Paint 事件。
注意
此範例會使用表單的 Refresh 方法,強制表單在清除筆劃時或應用程式模式變更時,重新繪製表單。
private void InkErase_OnPaint(object sender, PaintEventArgs e)
{
Strokes strokesToPaint = myInkCollector.Ink.Strokes;
myInkCollector.Renderer.Draw(e.Graphics, strokesToPaint);
switch (mode)
{
case ApplicationMode.CuspErase:
PaintCusps(e.Graphics, strokesToPaint);
break;
case ApplicationMode.IntersectErase:
PaintIntersections(e.Graphics, strokesToPaint);
break;
}
}
在 PaintCusps中,程式代碼會逐一查看每個筆劃中的每個尖點,並在其周圍繪製紅色圓圈。 筆劃的 PolylineCusps 屬性會傳回對應於筆劃尖點的點索引。 此外,請注意 Renderer 物件的 InkSpaceToPixel 方法,此方法會將點轉換成與 DrawEllipse 方法相關的座標。
private void PaintCusps(Graphics g, Strokes strokesToPaint)
{
foreach (Stroke currentStroke in strokesToPaint)
{
int[] cusps = currentStroke.PolylineCusps;
foreach (int i in cusps)
{
Point pt = currentStroke.GetPoint(i);
// Convert the X, Y position to Window based pixel coordinates
myInkCollector.Renderer.InkSpaceToPixel(g, ref pt);
// Draw a red circle as the cusp position
g.DrawEllipse(Pens.Red, pt.X-3, pt.Y-3, 6, 6);
}
}
}
在 PaintIntersections中,程式代碼會逐一查看每個筆劃,以尋找其與整個筆劃集的交集。 請注意,筆劃 FindIntersections 方法會傳遞 Strokes 集合,並傳回代表交集的浮點索引值陣列。 然後,程式代碼會計算每個交集的 X-Y 座標,並繪製其周圍的紅色圓圈。
private void PaintIntersections(Graphics g, Strokes strokesToPaint)
{
foreach (Stroke currentStroke in strokesToPaint)
{
float[] intersections = currentStroke.FindIntersections(strokesToPaint);
}
}
處理有兩端的畫筆
InkCollector 物件為 CursorDown、NewPackets和 Stroke 事件定義了三個事件處理程式。 每個事件處理程式都會檢查 Cursor 物件的 反向 屬性,以查看正在使用筆的哪一個末端。 將筆倒轉時:
-
myInkCollector_CursorDown方法會讓筆劃變得透明。 -
myInkCollector_NewPackets方法會消除筆劃。 -
myInkCollector_Stroke方法會取消事件。 NewPackets 事件會在 Stroke 事件發生之前生成。
追蹤游標
不論使用者是使用手寫筆還是滑鼠,都會產生 MouseMove 事件。 MouseMove 事件處理程式會先檢查目前模式是否為清除模式,以及是否按下任何滑鼠按鈕,如果這些狀態不存在,則會忽略事件。 然後,事件處理程式會使用 Renderer 將游標的圖元座標轉換成筆跡空間座標, 物件的 PixelToInkSpace 方法,並根據目前的清除模式呼叫其中一個程式代碼的清除方法。
清除筆觸
EraseStrokes 方法會採用游標在筆墨空間中的位置,併產生位於 HitTestRadius 單位內的筆劃集合。
currentStroke 參數會指定不應該刪除的 Stroke 物件。 然後會從收集器中刪除筆劃集合,並重新繪製表單。
private void EraseStrokes(Point pt, Stroke currentStroke)
{
Strokes strokesHit = myInkCollector.Ink.HitTest(pt, HitTestRadius);
if (null!=currentStroke && strokesHit.Contains(currentStroke))
{
strokesHit.Remove(currentStroke);
}
myInkCollector.Ink.DeleteStrokes(strokesHit);
if (strokesHit.Count > 0)
{
this.Refresh();
}
}
在交會處擦除
EraseAtIntersections 方法會逐一查看落在測試半徑內的每個筆劃,並產生一個陣列,該陣列包含該筆劃與集合中所有其他筆劃的交點。 如果找不到交集,則會刪除整個筆劃;否則,定位筆劃上距離測試點最近的點,然後找到該點兩側的交點,描述要移除的區段。
Stroke 物件的 Split 方法可用來將線段從筆劃的其餘部分中分離出來,然後刪除該線段,讓其餘的筆劃保持不變。 如同在 EraseStrokes中,表單會在方法返回前重新繪製。
private void EraseAtIntersections(Point pt)
{
Strokes strokesHit = myInkCollector.Ink.HitTest(pt, HitTestRadius);
foreach (Stroke currentStroke in strokesHit)
{
float[] intersections = currentStroke.FindIntersections(myInkCollector.Ink.Strokes);
...
float findex = currentStroke.NearestPoint(pt);
...
strokeToDelete = currentStroke.Split(intersections[i]);
...
}
...
}
在尖點處清除
針對每個位於測試半徑內的筆劃,EraseAtCusps 方法會從 Stroke 物件的 PolylineCusps 方法擷取尖點陣列。 筆劃的每一端也是一個尖點,因此如果筆劃只有兩個尖點,則會刪除完整的筆劃;否則,會找出筆劃中最接近測試點的點位置,並從該點的兩側定位出交點,用來描述需移除的區段。
Stroke 物件的 Split 方法用於將線段從筆劃的其他部分中分離,然後刪除該線段,使筆劃的其餘部分保持不變。 如同在 EraseStrokes中,表單會在方法傳回之前重新繪製。
private void EraseAtCusps(Point pt)
{
...
strokesHit = myInkCollector.Ink.HitTest(pt, HitTestRadius);
foreach (Stroke currentStroke in strokesHit)
{
int[] cusps = currentStroke.PolylineCusps;
...
float findex = currentStroke.NearestPoint(pt);
...
strokeToDelete = currentStroke.Split(cusps[i]);
myInkCollector.Ink.DeleteStroke(strokeToDelete);
...
}
...
}
關閉表單
表單的 Dispose 方法會處置 InkCollector 物件,myInkCollector。