810 lines
34 KiB
C#
810 lines
34 KiB
C#
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// File: InkErase.cs
|
|
// Simple Ink Erasing Sample Application
|
|
//
|
|
// This sample program demonstrates how to erase ink using hit
|
|
// testing. It has the following modes:
|
|
//
|
|
// 1. Ink: Allows the user to ink strokes.
|
|
// 2. Erase at Cusps: Displays the cusps as red points drawn over the
|
|
// strokes. The application reports a hit when the mouse is pressed
|
|
// and a circular region around the cursor intersects the stroke.
|
|
// When a hit occurs, the application splits the stroke at the
|
|
// cusps on either side of the hit and erases the corresponding stroke
|
|
// segment.
|
|
// 3. Erase at Intersections: Same as 2, except stroke intersections
|
|
// are used.
|
|
// 4. Erase Strokes: Uses hit testing to determine which strokes to delete.
|
|
//
|
|
// This sample application supports the inverted pen - if the
|
|
// pen is inverted in Ink mode, stroke erasing is performed.
|
|
//
|
|
// The features used are: InkCollector, Ink hit testing,
|
|
// deleting and splitting strokes, finding cusps and intersections,
|
|
// and using the inverted pen.
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
using System;
|
|
using System.Drawing;
|
|
using System.Windows.Forms;
|
|
|
|
// The Ink namespace, which contains the Tablet PC Platform API
|
|
using Microsoft.Ink;
|
|
|
|
namespace Microsoft.Samples.TabletPC.InkErase
|
|
{
|
|
|
|
/// <summary>
|
|
/// Enumeration of all possible application modes:
|
|
///
|
|
/// Ink: The user is drawing new strokes
|
|
/// CuspErase: The user is erasing at cusps
|
|
/// IntersectErase: The user is erasing at intersections
|
|
/// StrokeErase: The user is erasing strokes
|
|
///
|
|
/// </summary>
|
|
public enum ApplicationMode
|
|
{
|
|
Ink,
|
|
CuspErase,
|
|
IntersectErase,
|
|
StrokeErase
|
|
}
|
|
|
|
/// <summary>
|
|
/// Summary description for InkErase.
|
|
/// </summary>
|
|
public class InkErase : System.Windows.Forms.Form
|
|
{
|
|
// Declare the Ink Collector object
|
|
private InkCollector myInkCollector = null;
|
|
|
|
// Declare constant for the pen width used by this application.
|
|
// Note that this constant is in high metric units (1 unit = .01mm)
|
|
private const float MediumInkWidth = 100;
|
|
|
|
// Declare constant for the size of the hit test circle radius.
|
|
// Note that this constant is in high metric units (1 unit = .01mm)
|
|
private const float HitTestRadius = 30;
|
|
|
|
// Delcare constant for the radius of the painted cusp/intersection points
|
|
private const int StrokePointRadius = 3;
|
|
|
|
// Declare constant for the index of the x and y packet values
|
|
private const int XPacketIndex = 0;
|
|
private const int YPacketIndex = 1;
|
|
|
|
// The current application mode: inking, cusp erasing,
|
|
// intersection erasing, or stroke erasing
|
|
ApplicationMode mode = ApplicationMode.Ink;
|
|
|
|
#region Standard Template Code
|
|
|
|
private System.Windows.Forms.MenuItem miMainMode;
|
|
private System.Windows.Forms.MenuItem miInk;
|
|
private System.Windows.Forms.MenuItem miMainAction;
|
|
private System.Windows.Forms.MenuItem miClear;
|
|
private System.Windows.Forms.MenuItem miExit;
|
|
private System.Windows.Forms.MenuItem miIntersectErase;
|
|
private System.Windows.Forms.MenuItem miStrokeErase;
|
|
private System.Windows.Forms.MainMenu miMain;
|
|
private System.Windows.Forms.MenuItem miCuspErase;
|
|
private System.Windows.Forms.MenuItem miSeparator;
|
|
private System.ComponentModel.Container components = null;
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// The InkErase Sample Application form class
|
|
/// </summary>
|
|
public InkErase()
|
|
{
|
|
#region Standard Template Code
|
|
//
|
|
// Required for Windows Form Designer support
|
|
//
|
|
InitializeComponent();
|
|
#endregion
|
|
}
|
|
|
|
#region Standard Template Code
|
|
/// <summary>
|
|
/// Clean up any resources being used.
|
|
/// </summary>
|
|
protected override void Dispose( bool disposing )
|
|
{
|
|
if( disposing )
|
|
{
|
|
if (components != null)
|
|
{
|
|
components.Dispose();
|
|
}
|
|
if (myInkCollector != null)
|
|
{
|
|
myInkCollector.Dispose();
|
|
}
|
|
}
|
|
base.Dispose( disposing );
|
|
}
|
|
#endregion
|
|
|
|
#region Windows Form Designer generated code
|
|
/// <summary>
|
|
/// Required method for Designer support - do not modify
|
|
/// the contents of this method with the code editor.
|
|
/// </summary>
|
|
private void InitializeComponent()
|
|
{
|
|
this.miMain = new System.Windows.Forms.MainMenu();
|
|
this.miMainAction = new System.Windows.Forms.MenuItem();
|
|
this.miClear = new System.Windows.Forms.MenuItem();
|
|
this.miSeparator = new System.Windows.Forms.MenuItem();
|
|
this.miExit = new System.Windows.Forms.MenuItem();
|
|
this.miMainMode = new System.Windows.Forms.MenuItem();
|
|
this.miInk = new System.Windows.Forms.MenuItem();
|
|
this.miCuspErase = new System.Windows.Forms.MenuItem();
|
|
this.miIntersectErase = new System.Windows.Forms.MenuItem();
|
|
this.miStrokeErase = new System.Windows.Forms.MenuItem();
|
|
//
|
|
// miMain
|
|
//
|
|
this.miMain.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
|
|
this.miMainAction,
|
|
this.miMainMode});
|
|
//
|
|
// miMainAction
|
|
//
|
|
this.miMainAction.Index = 0;
|
|
this.miMainAction.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
|
|
this.miClear,
|
|
this.miSeparator,
|
|
this.miExit});
|
|
this.miMainAction.Text = "&Action";
|
|
//
|
|
// miClear
|
|
//
|
|
this.miClear.Index = 0;
|
|
this.miClear.Text = "&Clear";
|
|
this.miClear.Click += new System.EventHandler(this.miClear_Click);
|
|
//
|
|
// miSeparator
|
|
//
|
|
this.miSeparator.Index = 1;
|
|
this.miSeparator.Text = "-";
|
|
//
|
|
// miExit
|
|
//
|
|
this.miExit.Index = 2;
|
|
this.miExit.Text = "E&xit";
|
|
this.miExit.Click += new System.EventHandler(this.miExit_Click);
|
|
//
|
|
// miMainMode
|
|
//
|
|
this.miMainMode.Index = 1;
|
|
this.miMainMode.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
|
|
this.miInk,
|
|
this.miCuspErase,
|
|
this.miIntersectErase,
|
|
this.miStrokeErase});
|
|
this.miMainMode.Text = "&Mode";
|
|
//
|
|
// miInk
|
|
//
|
|
this.miInk.Checked = true;
|
|
this.miInk.Index = 0;
|
|
this.miInk.RadioCheck = true;
|
|
this.miInk.Shortcut = System.Windows.Forms.Shortcut.CtrlI;
|
|
this.miInk.Text = "&Ink";
|
|
this.miInk.Click += new System.EventHandler(this.miInk_Click);
|
|
//
|
|
// miCuspErase
|
|
//
|
|
this.miCuspErase.Index = 1;
|
|
this.miCuspErase.RadioCheck = true;
|
|
this.miCuspErase.Shortcut = System.Windows.Forms.Shortcut.CtrlC;
|
|
this.miCuspErase.Text = "Erase at &Cusps";
|
|
this.miCuspErase.Click += new System.EventHandler(this.miCuspErase_Click);
|
|
//
|
|
// miIntersectErase
|
|
//
|
|
this.miIntersectErase.Index = 2;
|
|
this.miIntersectErase.RadioCheck = true;
|
|
this.miIntersectErase.Shortcut = System.Windows.Forms.Shortcut.CtrlN;
|
|
this.miIntersectErase.Text = "Erase at I&ntersections";
|
|
this.miIntersectErase.Click += new System.EventHandler(this.miIntersectErase_Click);
|
|
//
|
|
// miStrokeErase
|
|
//
|
|
this.miStrokeErase.Index = 3;
|
|
this.miStrokeErase.RadioCheck = true;
|
|
this.miStrokeErase.Shortcut = System.Windows.Forms.Shortcut.CtrlS;
|
|
this.miStrokeErase.Text = "Erase &Strokes";
|
|
this.miStrokeErase.Click += new System.EventHandler(this.miStrokeErase_Click);
|
|
//
|
|
// InkErase
|
|
//
|
|
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
|
|
this.ClientSize = new System.Drawing.Size(292, 267);
|
|
this.Menu = this.miMain;
|
|
this.Name = "InkErase";
|
|
this.Text = "InkErase";
|
|
this.Load += new System.EventHandler(this.InkErase_Load);
|
|
this.Paint += new System.Windows.Forms.PaintEventHandler(this.InkErase_OnPaint);
|
|
this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.InkErase_OnMouseMove);
|
|
|
|
}
|
|
#endregion
|
|
|
|
#region Standard Template Code
|
|
/// <summary>
|
|
/// The main entry point for the application.
|
|
/// </summary>
|
|
[STAThread]
|
|
static void Main()
|
|
{
|
|
Application.Run(new InkErase());
|
|
}
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Event Handle from form's Load event
|
|
/// </summary>
|
|
/// <param name="sender">The control that raised the event.</param>
|
|
/// <param name="e">The event arguments.</param>
|
|
private void InkErase_Load(object sender, System.EventArgs e)
|
|
{
|
|
// Start the application in inking mode
|
|
mode = ApplicationMode.Ink;
|
|
|
|
// Create a new ink collector and assign it to this form's window
|
|
myInkCollector = new InkCollector(Handle);
|
|
|
|
// Turn off auto-redrawing since this sample application
|
|
// needs to display the stroke cusps as red points over the strokes.
|
|
// If autoredraw is enabled, the strokes will be drawn over
|
|
// the red points, which will make the cusps hard to see.
|
|
myInkCollector.AutoRedraw = false;
|
|
|
|
// Set the pen width to be a medium width
|
|
myInkCollector.DefaultDrawingAttributes.Width = MediumInkWidth;
|
|
|
|
// Hook event handle for Cursor down event to myInkCollector_CursorDown.
|
|
// This is necessary since the application needs to check if the cursor
|
|
// is inverted and use the result to determine the visibility of the ink.
|
|
myInkCollector.CursorDown += new InkCollectorCursorDownEventHandler(myInkCollector_CursorDown);
|
|
|
|
// Hook event handle for NewPackets event to myInkCollector_NewPackets.
|
|
// This is necessary since the application needs to examine new packets
|
|
// when the cursor is inverted and use them to determine whether
|
|
// any strokes should be erased.
|
|
myInkCollector.NewPackets += new InkCollectorNewPacketsEventHandler(myInkCollector_NewPackets);
|
|
|
|
// Hook event handle for the Stroke event to myInkCollector_Stroke.
|
|
// This is necessary since the application needs to cancel strokes drawn
|
|
// while the cursor is inverted.
|
|
myInkCollector.Stroke += new InkCollectorStrokeEventHandler(myInkCollector_Stroke);
|
|
|
|
// Turn the ink collector on
|
|
myInkCollector.Enabled = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event Handle from MouseMove event.
|
|
/// </summary>
|
|
/// <param name="sender">The control that raised the event.</param>
|
|
/// <param name="e">The event arguments.</param>
|
|
private void InkErase_OnMouseMove(object sender, MouseEventArgs e)
|
|
{
|
|
|
|
// If the application is in erase mode and a mouse button is
|
|
// pressed, perform a hit test to determine which stroke segments
|
|
// to erase (if any).
|
|
if ( (ApplicationMode.Ink != mode) && (MouseButtons.None != MouseButtons) )
|
|
{
|
|
|
|
Point pt = new Point(e.X, e.Y);
|
|
|
|
// Convert the specified point from pixel to ink space coordinates
|
|
using (Graphics g = CreateGraphics())
|
|
{
|
|
myInkCollector.Renderer.PixelToInkSpace(g, ref pt);
|
|
}
|
|
|
|
switch(mode)
|
|
{
|
|
case ApplicationMode.CuspErase:
|
|
EraseAtCusps(pt);
|
|
break;
|
|
case ApplicationMode.IntersectErase:
|
|
EraseAtIntersections(pt);
|
|
break;
|
|
case ApplicationMode.StrokeErase:
|
|
EraseStrokes(pt,null);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event Handle from Paint event. It is necessary to handle the
|
|
/// paint event since this sample needs to draw red points to indicate
|
|
/// the strokes' cusps.
|
|
/// </summary>
|
|
/// <param name="sender">The control that raised the event.</param>
|
|
/// <param name="e">The event arguments.</param>
|
|
private void InkErase_OnPaint(object sender, PaintEventArgs e)
|
|
{
|
|
// Get the strokes to paint from the ink
|
|
Strokes strokesToPaint = myInkCollector.Ink.Strokes;
|
|
|
|
// Draw the strokes - note that it is necessary to manually
|
|
// paint the strokes since auto-redrawing is set to false.
|
|
myInkCollector.Renderer.Draw(e.Graphics, strokesToPaint);
|
|
|
|
switch (mode)
|
|
{
|
|
case ApplicationMode.CuspErase:
|
|
PaintCusps(e.Graphics, strokesToPaint);
|
|
break;
|
|
case ApplicationMode.IntersectErase:
|
|
PaintIntersections(e.Graphics, strokesToPaint);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event Handle from Action->Clear menu.
|
|
/// </summary>
|
|
/// <param name="sender">The control that raised the event.</param>
|
|
/// <param name="e">The event arguments.</param>
|
|
private void miClear_Click(object sender, System.EventArgs e)
|
|
{
|
|
Strokes strokesToDelete = myInkCollector.Ink.Strokes;
|
|
|
|
// Check to ensure that the ink collector isn't currently
|
|
// in the middle of a stroke before clearing the ink.
|
|
// Deleting a stroke that is currently being collected
|
|
// will result in an error condition.
|
|
if (!myInkCollector.CollectingInk)
|
|
{
|
|
myInkCollector.Ink.DeleteStrokes(strokesToDelete);
|
|
|
|
miInk_Click(sender, e);
|
|
}
|
|
else
|
|
{
|
|
MessageBox.Show("Cannot clear ink while the ink collector is busy.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event Handle from Action->Exit menu.
|
|
/// </summary>
|
|
/// <param name="sender">The control that raised the event.</param>
|
|
/// <param name="e">The event arguments.</param>
|
|
private void miExit_Click(object sender, System.EventArgs e)
|
|
{
|
|
myInkCollector.Enabled = false;
|
|
Application.Exit();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event Handle from Mode->Ink menu.
|
|
/// </summary>
|
|
/// <param name="sender">The control that raised the event.</param>
|
|
/// <param name="e">The event arguments.</param>
|
|
private void miInk_Click(object sender, System.EventArgs e)
|
|
{
|
|
UpdateApplicationMode(ApplicationMode.Ink);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event Handle from Mode->Cusp Erase menu.
|
|
/// </summary>
|
|
/// <param name="sender">The control that raised the event.</param>
|
|
/// <param name="e">The event arguments.</param>
|
|
private void miCuspErase_Click(object sender, System.EventArgs e)
|
|
{
|
|
UpdateApplicationMode(ApplicationMode.CuspErase);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event Handle from Mode->Intersect Erase menu.
|
|
/// </summary>
|
|
/// <param name="sender">The control that raised the event.</param>
|
|
/// <param name="e">The event arguments.</param>
|
|
private void miIntersectErase_Click(object sender, System.EventArgs e)
|
|
{
|
|
UpdateApplicationMode(ApplicationMode.IntersectErase);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event Handle from Mode->Stroke Erase menu.
|
|
/// </summary>
|
|
/// <param name="sender">The control that raised the event.</param>
|
|
/// <param name="e">The event arguments.</param>
|
|
private void miStrokeErase_Click(object sender, System.EventArgs e)
|
|
{
|
|
UpdateApplicationMode(ApplicationMode.StrokeErase);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method to update the application mode
|
|
/// </summary>
|
|
/// <param name="newMode">The new mode</param>
|
|
private void UpdateApplicationMode(ApplicationMode newMode)
|
|
{
|
|
// Turn on/off the ink collector
|
|
myInkCollector.Enabled = (ApplicationMode.Ink == newMode);
|
|
|
|
// Update the state of the Ink and Erase menu items
|
|
miInk.Checked = (ApplicationMode.Ink == newMode);
|
|
miCuspErase.Checked = (ApplicationMode.CuspErase == newMode);
|
|
miIntersectErase.Checked = (ApplicationMode.IntersectErase == newMode);
|
|
miStrokeErase.Checked = (ApplicationMode.StrokeErase == newMode);
|
|
|
|
mode = newMode;
|
|
|
|
Refresh();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event Handle from Cursor event
|
|
/// </summary>
|
|
/// <param name="sender">The control that raised the event.</param>
|
|
/// <param name="e">The event arguments.</param>
|
|
public void myInkCollector_CursorDown(object sender, InkCollectorCursorDownEventArgs e)
|
|
{
|
|
// If the pen is inverted, this application will perform stroke
|
|
// erasing; since we do not want to show the pen while the user
|
|
// is erasing, make this stroke transparent.
|
|
if (e.Cursor.Inverted)
|
|
{
|
|
e.Stroke.DrawingAttributes.Transparency = 255;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event Handler from Ink Collector's NewPackets event
|
|
///
|
|
/// This event is fired when the Ink Collector receives
|
|
/// new packet data.
|
|
/// </summary>
|
|
/// <param name="sender">The control that raised the event.</param>
|
|
/// <param name="e">The event arguments.</param>
|
|
private void myInkCollector_NewPackets(object sender, InkCollectorNewPacketsEventArgs e)
|
|
{
|
|
// If the cursor is inverted
|
|
if (e.Cursor.Inverted)
|
|
{
|
|
|
|
// retrieve the size of each packet
|
|
int packetSize = e.Stroke.PacketSize;
|
|
|
|
Point pt = Point.Empty;
|
|
|
|
// Perform a hit test with each point and delete
|
|
// the hit strokes
|
|
for (int i = 0; i < e.PacketCount; i++)
|
|
{
|
|
// retrieve the x and y packet values
|
|
pt.X = e.PacketData[i*packetSize+XPacketIndex];
|
|
pt.Y = e.PacketData[i*packetSize+YPacketIndex];
|
|
|
|
EraseStrokes(pt,e.Stroke);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event Handle from Stroke event
|
|
/// </summary>
|
|
/// <param name="sender">The control that raised the event.</param>
|
|
/// <param name="e">The event arguments.</param>
|
|
public void myInkCollector_Stroke(object sender, InkCollectorStrokeEventArgs e )
|
|
{
|
|
if (e.Cursor.Inverted)
|
|
{
|
|
e.Cancel = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method to paint the stroke collection's cusps
|
|
/// </summary>
|
|
/// <param name="g">The graphics object to use for painting</param>
|
|
/// <param name="strokesToPaint">The collection of strokes to paint</param>
|
|
private void PaintCusps(Graphics g, Strokes strokesToPaint)
|
|
{
|
|
// now draw PolylineCusp points
|
|
foreach (Stroke currentStroke in strokesToPaint)
|
|
{
|
|
|
|
// Retrieve the cusps of the stroke. The cusps mark the points where
|
|
// the stroke changes direction abruptly. A segment is defined as the
|
|
// points between two cusps.
|
|
int[] cusps = currentStroke.PolylineCusps;
|
|
|
|
// Draw each cusp in the stroke
|
|
foreach (int i in cusps)
|
|
{
|
|
|
|
// Get the X, Y position of the cusp
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method to paint the stroke collection's intersections
|
|
/// </summary>
|
|
/// <param name="g">The graphics object to use for painting</param>
|
|
/// <param name="strokesToPaint">The collection of strokes to paint</param>
|
|
private void PaintIntersections(Graphics g, Strokes strokesToPaint)
|
|
{
|
|
// Draw the intersections of each stroke as little red circles
|
|
foreach (Stroke currentStroke in strokesToPaint)
|
|
{
|
|
|
|
// Get the intersections of the stroke
|
|
float[] intersections = currentStroke.FindIntersections(strokesToPaint);
|
|
|
|
Point[] points = currentStroke.GetPoints();
|
|
|
|
// Draw each intersection in the stroke
|
|
foreach (float fi in intersections)
|
|
{
|
|
// Get the point before the FINDEX
|
|
Point ptIntersect = currentStroke.GetPoint((int)fi);
|
|
|
|
// Find the fractional part of the FINDEX
|
|
float fiFraction = fi - (int)fi;
|
|
|
|
// if the fi does not have a fractional part, we have already
|
|
// found the intersection point. Otherwise, use the FINDEX to
|
|
// calculate the interpolated intersection point on the stroke
|
|
if (fiFraction > 0.0f)
|
|
{
|
|
Point ptNextIntersect = currentStroke.GetPoint((int)fi + 1);
|
|
ptIntersect.X += (int)((ptNextIntersect.X - ptIntersect.X) * fiFraction);
|
|
ptIntersect.Y += (int)((ptNextIntersect.Y - ptIntersect.Y) * fiFraction);
|
|
|
|
}
|
|
|
|
// Convert the X, Y position to Window based pixel coordinates
|
|
myInkCollector.Renderer.InkSpaceToPixel(g, ref ptIntersect);
|
|
|
|
// Draw a red circle as the intersection position
|
|
g.DrawEllipse(Pens.Red, ptIntersect.X-3, ptIntersect.Y-3, 6, 6);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method that performs a hit test using the specified point.
|
|
/// When a hit occurs, the application splits the stroke at the cusps
|
|
/// on either side of the hit and erases the corresponding stroke segment.
|
|
/// </summary>
|
|
/// <param name="pt">The point to use for hit testing.</param>
|
|
private void EraseAtCusps(Point pt)
|
|
{
|
|
|
|
// Declare the collection of strokes returned from HitTest
|
|
Strokes strokesHit = null;
|
|
|
|
// Use HitTest to find the collection of strokes that are intersected
|
|
// by the point. The HitTestRadius constant is used to specify the
|
|
// radius of the hit test circle in ink space coordinates (1 unit = .01mm).
|
|
strokesHit = myInkCollector.Ink.HitTest(pt, HitTestRadius);
|
|
|
|
// Loop over each stroke returned from the hit test to determine
|
|
// which portion to erase...
|
|
foreach (Stroke currentStroke in strokesHit)
|
|
{
|
|
|
|
// Retrieve the cusps of the stroke. The cusps mark the points where
|
|
// the stroke changes direction abruptly. A segment is defined as the
|
|
// points between two cusps.
|
|
int[] cusps = currentStroke.PolylineCusps;
|
|
|
|
// If there are 1 or two cusps, it's a single stroke - delete the
|
|
// entire stroke.
|
|
if (cusps.Length <= 2)
|
|
{
|
|
myInkCollector.Ink.DeleteStroke(currentStroke);
|
|
}
|
|
|
|
// If there are more than 2 cusps, determine which cusps bound the
|
|
// hit-tested portion of the stroke, split the stroke at these cusps,
|
|
// and delete the stroke that defines the segment we hit-tested.
|
|
else
|
|
{
|
|
// Get the FINDEX of the nearest point on the stroke. An FINDEX
|
|
// is a float value representing a location somewhere between two
|
|
// points in the stroke. For instance, 0.0 is the first point in
|
|
// the stroke. 1.0 is the second point in the stroke. 0.5 is halfway
|
|
// between the first and second points.
|
|
float findex = currentStroke.NearestPoint(pt);
|
|
|
|
// Declare the stroke segment to delete
|
|
Stroke strokeToDelete = null;
|
|
|
|
// Cycle through each cusp of the stroke to determine
|
|
// which cusps bound the hit-tested portion of the stroke...
|
|
for(int i = cusps.Length-2; i>=0; i--)
|
|
{
|
|
// If this cusp is less than the findex, then split
|
|
// the stroke at this cusp and the cusp immediately
|
|
// after it
|
|
if (cusps[i]<=findex)
|
|
{
|
|
// Provided we aren't at the end of the stroke, split at
|
|
// cusp i+1.
|
|
if (i < (cusps.Length-2))
|
|
{
|
|
strokeToDelete = currentStroke.Split(cusps[i+1]);
|
|
}
|
|
|
|
// If the hit occurred between the first and second cusp,
|
|
// delete the stroke. Keep in mind that the stroke has
|
|
// already been split at index 1, so the delete will only
|
|
// remove the beginning portion of the stroke (as desired).
|
|
if (i==0)
|
|
{
|
|
myInkCollector.Ink.DeleteStroke(currentStroke);
|
|
}
|
|
// Otherwise, split the stroke at the current cusp and
|
|
// delete the result. Keep in mind that the stroke has
|
|
// already been split at index i+1, so the delete will
|
|
// remove the segment from cusp i to i+1.
|
|
else
|
|
{
|
|
strokeToDelete = currentStroke.Split(cusps[i]);
|
|
myInkCollector.Ink.DeleteStroke(strokeToDelete);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (strokesHit.Count > 0)
|
|
{
|
|
// Repaint the screen to reflect the change
|
|
this.Refresh();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method that performs a hit test using the specified point.
|
|
/// When a hit occurs, the application splits the stroke at the intersections
|
|
/// on either side of the hit and erases the corresponding stroke segment.
|
|
/// </summary>
|
|
/// <param name="pt">The point to use for hit testing.</param>
|
|
private void EraseAtIntersections(Point pt)
|
|
{
|
|
|
|
// Use HitTest to find the collection of strokes that are intersected
|
|
// by the point. The HitTestRadius constant is used to specify the
|
|
// radius of the hit test circle in ink space coordinates (1 unit = .01mm).
|
|
Strokes strokesHit = myInkCollector.Ink.HitTest(pt, HitTestRadius);
|
|
|
|
// Loop over each stroke returned from the hit test to determine
|
|
// which portion to erase...
|
|
foreach (Stroke currentStroke in strokesHit)
|
|
{
|
|
|
|
// Retrieve the intersection of the stroke.
|
|
float[] intersections = currentStroke.FindIntersections(myInkCollector.Ink.Strokes);
|
|
|
|
// If there aren't any intersections, delete the entire stroke.
|
|
if (intersections.Length <= 0)
|
|
{
|
|
myInkCollector.Ink.DeleteStroke(currentStroke);
|
|
}
|
|
|
|
// If there is at least one intersection, determine which
|
|
// intersections bound the hit-tested portion of the stroke,
|
|
// split the stroke at these intersections, and delete the stroke
|
|
// that defines the segment we hit-tested.
|
|
else
|
|
{
|
|
// Get the FINDEX of the nearest point on the stroke. An FINDEX
|
|
// is a float value representing a location somewhere between two
|
|
// points in the stroke. For instance, 0.0 is the first point in
|
|
// the stroke. 1.0 is the second point in the stroke. 0.5 is halfway
|
|
// between the first and second points.
|
|
float findex = currentStroke.NearestPoint(pt);
|
|
|
|
// If the hit occured before the first intersection, split the stroke
|
|
// at the current intersection and delete the beginning of the
|
|
// stroke
|
|
if (findex < intersections[0])
|
|
{
|
|
currentStroke.Split (intersections[0]);
|
|
myInkCollector.Ink.DeleteStroke(currentStroke);
|
|
}
|
|
else
|
|
{
|
|
// Declare the stroke segment to delete
|
|
Stroke strokeToDelete = null;
|
|
|
|
// Cycle through each intersection of the stroke to determine
|
|
// which intersections bound the hit-tested portion of the stroke...
|
|
for(int i = intersections.Length-1; i>=0; i--)
|
|
{
|
|
|
|
// If this intersection is less than the findex, the intersection
|
|
// occurs between this intersection and the one after it
|
|
if (intersections[i]<=findex)
|
|
{
|
|
// Provided we aren't at the end of the stroke, split at
|
|
// intersection i+1.
|
|
if (i < (intersections.Length-1))
|
|
{
|
|
strokeToDelete = currentStroke.Split(intersections[i+1]);
|
|
}
|
|
|
|
// Split the stroke at the current intersection. Keep in
|
|
// mind that the stroke has already been split at index i+1,
|
|
// so the delete will remove the segment from intersection i
|
|
// to i+1.
|
|
strokeToDelete = currentStroke.Split(intersections[i]);
|
|
myInkCollector.Ink.DeleteStroke(strokeToDelete);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (strokesHit.Count > 0)
|
|
{
|
|
// Repaint the screen to reflect the change
|
|
this.Refresh();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method that performs a hit test using the specified point.
|
|
/// It deletes all strokes that were hit by the point
|
|
/// </summary>
|
|
/// <param name="pt">The point to use for hit testing</param>
|
|
private void EraseStrokes(Point pt, Stroke currentStroke)
|
|
{
|
|
|
|
// Use HitTest to find the collection of strokes that are intersected
|
|
// by the point. The HitTestRadius constant is used to specify the
|
|
// radius of the hit test circle in ink space coordinates (1 unit = .01mm).
|
|
Strokes strokesHit = myInkCollector.Ink.HitTest(pt, HitTestRadius);
|
|
|
|
if (null!=currentStroke && strokesHit.Contains(currentStroke))
|
|
{
|
|
strokesHit.Remove(currentStroke);
|
|
}
|
|
|
|
// Delete all strokes that were hit by the point
|
|
myInkCollector.Ink.DeleteStrokes(strokesHit);
|
|
|
|
if (strokesHit.Count > 0)
|
|
{
|
|
// Repaint the screen to reflect the change
|
|
this.Refresh();
|
|
}
|
|
}
|
|
}
|
|
}
|