Intercepting Input from the Stylus
The System.Windows.Input.StylusPlugIns architecture provides a mechanism for implementing low-level control over Stylus input and the creation of digital ink Stroke objects. The StylusPlugIn class provides a mechanism for you to implement custom behavior and apply it to the stream of data coming from the stylus device for the optimal performance.
This topic contains the following subsections:
Architecture
Implementing StylusPlugins
Adding Your Plugin to an InkCanvas
Conclusion
Architecture
The StylusPlugIn is the evolution of the StylusInput APIs, described in Accessing and Manipulating Pen Input, in the Microsoft Windows XP Tablet PC Edition Software Development Kit 1.7.
Each UIElement has a StylusPlugIns property that is a StylusPlugInCollection. You can add a StylusPlugIn to an element's StylusPlugIns property to manipulate StylusPoint data as it is generated. StylusPoint data consists of all the properties supported by the system digitizer, including the X and Y point data, as well as PressureFactor data.
Your StylusPlugIn objects are inserted directly into the stream of data coming from the Stylus device when you add the StylusPlugIn to the StylusPlugIns property. The order in which plug-ins are added to the StylusPlugIns collection dictates the order in which they will receive StylusPoint data. For example, if you add a filter plug-in that restricts input to a particular region, and then add a plug-in that recognizes gestures as they are written, the plug-in that recognizes gestures will receive filtered StylusPoint data.
Implementing Stylus Plug-ins
To implement a plug-in, derive a class from StylusPlugIn. This class is applied o the stream of data as it comes in from the Stylus. In this class you can modify the values of the StylusPoint data.
Caution
If a StylusPlugIn throws or causes an exception, the application will close. You should thoroughly test controls that consume a StylusPlugIn and only use a control if you are certain the StylusPlugIn will not throw an exception.
The following example demonstrates a plug-in that restricts the stylus input by modifying the X and Y values in the StylusPoint data as it comes in from the Stylus device.
Imports System
Imports System.Windows.Media
Imports System.Windows
Imports System.Windows.Input.StylusPlugIns
Imports System.Windows.Input
Imports System.Windows.Ink
...
' A StylusPlugin that restricts the input area.
Class FilterPlugin
Inherits StylusPlugIn
Protected Overrides Sub OnStylusDown(ByVal rawStylusInput As RawStylusInput)
' Call the base class before modifying the data.
MyBase.OnStylusDown(rawStylusInput)
' Restrict the stylus input.
Filter(rawStylusInput)
End Sub 'OnStylusDown
Protected Overrides Sub OnStylusMove(ByVal rawStylusInput As RawStylusInput)
' Call the base class before modifying the data.
MyBase.OnStylusMove(rawStylusInput)
' Restrict the stylus input.
Filter(rawStylusInput)
End Sub 'OnStylusMove
Protected Overrides Sub OnStylusUp(ByVal rawStylusInput As RawStylusInput)
' Call the base class before modifying the data.
MyBase.OnStylusUp(rawStylusInput)
' Restrict the stylus input
Filter(rawStylusInput)
End Sub 'OnStylusUp
Private Sub Filter(ByVal rawStylusInput As RawStylusInput)
' Get the StylusPoints that have come in.
Dim stylusPoints As StylusPointCollection = rawStylusInput.GetStylusPoints()
' Modify the (X,Y) data to move the points
' inside the acceptable input area, if necessary.
Dim i As Integer
For i = 0 To stylusPoints.Count - 1
Dim sp As StylusPoint = stylusPoints(i)
If sp.X < 50 Then
sp.X = 50
End If
If sp.X > 250 Then
sp.X = 250
End If
If sp.Y < 50 Then
sp.Y = 50
End If
If sp.Y > 250 Then
sp.Y = 250
End If
stylusPoints(i) = sp
Next i
' Copy the modified StylusPoints back to the RawStylusInput.
rawStylusInput.SetStylusPoints(stylusPoints)
End Sub 'Filter
End Class 'FilterPlugin
using System;
using System.Windows.Media;
using System.Windows;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Input;
using System.Windows.Ink;
...
// A StylusPlugin that restricts the input area.
class FilterPlugin : StylusPlugIn
{
protected override void OnStylusDown(RawStylusInput rawStylusInput)
{
// Call the base class before modifying the data.
base.OnStylusDown(rawStylusInput);
// Restrict the stylus input.
Filter(rawStylusInput);
}
protected override void OnStylusMove(RawStylusInput rawStylusInput)
{
// Call the base class before modifying the data.
base.OnStylusMove(rawStylusInput);
// Restrict the stylus input.
Filter(rawStylusInput);
}
protected override void OnStylusUp(RawStylusInput rawStylusInput)
{
// Call the base class before modifying the data.
base.OnStylusUp(rawStylusInput);
// Restrict the stylus input
Filter(rawStylusInput);
}
private void Filter(RawStylusInput rawStylusInput)
{
// Get the StylusPoints that have come in.
StylusPointCollection stylusPoints = rawStylusInput.GetStylusPoints();
// Modify the (X,Y) data to move the points
// inside the acceptable input area, if necessary.
for (int i = 0; i < stylusPoints.Count; i++)
{
StylusPoint sp = stylusPoints[i];
if (sp.X < 50) sp.X = 50;
if (sp.X > 250) sp.X = 250;
if (sp.Y < 50) sp.Y = 50;
if (sp.Y > 250) sp.Y = 250;
stylusPoints[i] = sp;
}
// Copy the modified StylusPoints back to the RawStylusInput.
rawStylusInput.SetStylusPoints(stylusPoints);
}
}
Adding Your Plug-in to an InkCanvas
The easiest way to use your custom plug-in is to implement a class that derives from InkCanvas and add it to the StylusPlugIns property.
The following example demonstrates a custom InkCanvas that filters the ink.
Public Class FilterInkCanvas
Inherits InkCanvas
Private filter As New FilterPlugin()
Public Sub New()
Me.StylusPlugIns.Add(filter)
End Sub 'New
End Class 'FilterInkCanvas
public class FilterInkCanvas : InkCanvas
{
FilterPlugin filter = new FilterPlugin();
public FilterInkCanvas()
: base()
{
this.StylusPlugIns.Add(filter);
}
}
If you add a FilterInkCanvas to your application and run it, you will notice that the ink isn't restricted to a region until after the user completes a stroke. This is because the InkCanvas has a DynamicRenderer property, which is a StylusPlugIn and is already a member of the StylusPlugIns collection. The custom StylusPlugIn you added to the StylusPlugIns collection receives the StylusPoint data after DynamicRenderer receives data. As a result, the StylusPoint data will not be filtered until after the user lifts the pen to end a stroke. To filter the ink as the user draws it, you must insert the FilterPlugin before the DynamicRenderer.
The following C# code demonstrates a custom InkCanvas that filters the ink as it is drawn.
Public Class DynamicallyFilteredInkCanvas
Inherits InkCanvas
Private filter As New FilterPlugin()
Public Sub New()
Dim dynamicRenderIndex As Integer = Me.StylusPlugIns.IndexOf(Me.DynamicRenderer)
Me.StylusPlugIns.Insert(dynamicRenderIndex, filter)
End Sub 'New
End Class 'DynamicallyFilteredInkCanvas
public class DynamicallyFilteredInkCanvas : InkCanvas
{
FilterPlugin filter = new FilterPlugin();
public DynamicallyFilteredInkCanvas()
: base()
{
int dynamicRenderIndex =
this.StylusPlugIns.IndexOf(this.DynamicRenderer);
this.StylusPlugIns.Insert(dynamicRenderIndex, filter);
}
}
Conclusion
By deriving your own StylusPlugIn classes and inserting them into StylusPlugInCollection collections, you can greatly enhance the behavior of your digital ink. You have access to the StylusPoint data as it is generated, giving you the opportunity to customize the Stylus input. Because you have such low-level access to the StylusPoint data, you can implement ink collection and rendering with optimal performance for your application.