RealTimeStylus Plug-in Sample

RealTimeStylus Plug-in Sample

Description of RealTimeStylus Plug-in sample for the Tablet PC.

This application demonstrates working with the RealTimeStylus class. For a detailed overview of the StylusInput APIs, including the RealTimeStylus class, see Accessing and Manipulating Pen Input. For information about synchronous and asynchronous plug-ins, see Plug-ins and the RealTimeStylus Class.

Overview of the Sample

Plug-ins, objects that implement the IStylusSyncPlugin or IStylusAsyncPlugin interface, can be added to a RealTimeStylus object. This sample application uses several types of plug-in:

  • Packet Filter Plug-in: Modifies packets. The packet filter plug-in in this sample modifies packet information by constraining all (x,y) packet data within a rectangular area.
  • Custom Dynamic Renderer Plug-in: Modifies dynamic rendering qualities. The custom dynamic rendering plug-in in this sample modifies the way ink is rendered by drawing a small circle around each (x,y) point on a stroke.
  • Dynamic Renderer Plug-in: Modifies dynamic rendering qualities. This sample demonstrates use of the DynamicRenderer object provided by the Microsoft® Windows® XP Tablet PC Edition Software Development Kit (SDK) Platform application programming interface (API) as a plug-in to handle dynamic rendering of ink.
  • Gesture Recognizer Plug-in: Recognizes application gestures. This sample demonstrates use of the GestureRecognizer object provided by the Tablet PC SDK Platform API as a plug-in to recognize application gestures (when running on a system with the Microsoft gesture recognizer present).

In addition, this sample provides a user interface that enables the user to add, remove, and change the order of each plug-in in the collection. The sample solution contains two projects, RealTimeStylusPluginApp and RealTimeStylusPlugins. RealTimeStylusPluginApp contains the user interface for the sample. RealTimeStylusPlugins contains the implementations of the plug-ins. The RealTimeStylusPlugins project defines the RealTimeStylusPlugins namespace, which contains the packet filter and custom dynamic renderer plug-ins. This namespace is referenced by the RealTimeStylusPluginApp project. The RealTimeStylusPlugins project uses the Microsoft.Ink, Microsoft.StylusInput, and Microsoft.StylusInput.PluginData namespaces.

For an overview of the Microsoft.StylusInput and Microsoft.StylusInput.PluginData namespaces, see Architecture of the StylusInput APIs.

Packet Filter Plug-in

The packet filter plug-in is a synchronous plug-in that demonstrates packet modification. Specifically, it defines a rectangle on the form. Any packets that are drawn outside the region are rendered inside the region. The plug-in class, PacketFilterPlugin, registers for notification of StylusDown, StylusUp, and Packets pen input events. The class implements the StylusDown, StylusUp, and Packets methods defined on IStylusSyncPlugin class.

The public constructor for PacketFilterPlugin requires a Rectangle Leave Site structure. This rectangle defines the rectangular area, in ink space coordinates (.01mm = 1 HIMETRIC unit), in which packets will be contained. The rectangle is held in a private field, rectangle.

public class PacketFilterPlugin:IStylusSyncPlugin
{
    private System.Drawing.Rectangle rectangle = System.Drawing.Rectangle.Empty;
    public PacketFilterPlugin(Rectangle r)
    {
        rectangle = r;
    }
    // ...

The PacketFilterPlugin class registers for event notifications by implementing the get accessor for the DataInterest property. In this case, the plug-in has interested in responding to the StylusDown, Packets, StylusUp, and Error notifications. The sample returns these values as defined in the DataInterestMask enumeration. The StylusDown method is called when the pen tip contacts the tablet surface. The StylusUp method is called when the pen tip leaves the tablet surface. The Packets method is called when the RealTimeStylus object receives packets. The Error method is called when either the RealTimeStylus object or a plug-in that is earlier in the queue throws an error.

public DataInterestMask DataInterest
{
    get
    {
        return DataInterestMask.StylusDown |
               DataInterestMask.Packets |
               DataInterestMask.StylusUp |
               DataInterestMask.Error;
    }
}
    //...

The PacketFilterPlugin class handles most of these notifications in a helper method, ModifyPacketData. The ModifyPacketData method gets the x and y values for each new packet from the PacketsData class. If either value is outside the rectangle, the method replaces the value with the nearest point that still falls within the rectangle. This is an example of how a plug-in can replace packet data as it is received from the pen-input stream.

private void ModifyPacketData(StylusDataBase data)
{
    for (int i = 0; i < data.Count ; i += data.PacketPropertyCount)
    {
        // packet data always has x followed by y followed by the rest
        int x = data[i];
        int y = data[i+1];

        // Constrain points to the input rectangle
        x = Math.Max(x, rectangle.Left);
        x = Math.Min(x, rectangle.Right);
        y = Math.Max(y, rectangle.Top);
        y = Math.Min(y, rectangle.Bottom);

        // If necessary, modify the x,y packet data
        if (x != data[i])
        {
            data[i] = x;
        }
        if (y != data[i+1])
        {
            data[i+1] = y;
        }
    }
}

Custom Dynamic Renderer Plug-in

The CustomDynamicRenderer class also implements the IStylusSyncPlugin class to receive pen-input notifications. It then handles the packet notification to draw a small circle around each new packet point.

The class contains a Graphics Leave Site variable that holds a reference to the graphics object passed into the class contructor. This is the graphics object used for dynamic rendering.

private Graphics myGraphics;

public CustomDynamicRendererPlugin(Graphics g)
{
    myGraphics = g;
}
        //...
            

When the custom dynamic renderer plug-in receives a Packets notification, it extracts the (x,y) data and draws a small green circle around the point.

public void Packets(RealTimeStylus sender,  PacketsData data)
{
    for (int i = 0; i < data.Count; i += data.PacketPropertyCount)
    {
        // Packet data always has x followed by y followed by the rest
        Point point = new Point(data[i], data[i+1]);

        // Since the packet data is in Ink Space coordinates, we need to convert to Pixels...
        point.X = (int)Math.Round((float)point.X * (float)myGraphics.DpiX/2540.0F);
        point.Y = (int)Math.Round((float)point.Y * (float)myGraphics.DpiY/2540.0F);

        // Draw a circle corresponding to the packet
        myGraphics.DrawEllipse(Pens.Green, point.X - 2, point.Y - 2, 4, 4);
    }
}

This is an example of custom rendering based on the pen-input stream.

The RealTimeStylusPluginApp Project

The RealTimeStylusPluginApp project demonstrates the plug-ins previously described, as well as the GestureRecognizer and DynamicRenderer plug-ins. The project's user interface consists of:

  • A Form Leave Site that contains a GroupBox Leave Site control used to define the ink entry area.
  • A CheckedListBox Leave Site control to list and select the available plug-ins.
  • A pair of buttons Leave Site to allow for re-ordering the plug-ins.

The project defines a structure, PlugInListItem, to make managing the plug-ins used in the project easier. The PlugInListItem structure contains the plug-in and a description.

The RealTimeStylusPluginApp class itself implements the IStylusAsyncPlugin class. This is necessary so that the RealTimeStylusPluginApp class can be notified when the GestureRecognizer plug-in adds gesture data to the output queue. The application registers for notification of CustomStylusDataAdded. When gesture data is received, RealTimeStylusPluginApp places a description of it on the status bar at the bottom of the form.

public void CustomStylusDataAdded(RealTimeStylus sender, CustomStylusData data)
{
    if (data.CustomDataId == GestureRecognizer.GestureRecognitionDataGuid)
    {
        GestureRecognitionData grd = data.Data as GestureRecognitionData;
        if (grd != null)
        {
            if (grd.Count > 0)
            {
                GestureAlternate ga = grd[0];
                sbGesture.Text = "Gesture=" + ga.Id + ", Confidence=" + ga.Confidence;
            }
        }
    }
}

Note: In the CustomStylusDataAdded implementation, it is interesting that you can identify the custom gesture data in the output queue either by GUID (by using the GestureRecognitionDataGuid field) or by type (by using the result from the as statement). The sample uses both identification techniques for demonstation purposes. Either approach alone is also valid.

In the Form's Load event handler, the applications creates instances of the PacketFilter and CustomDynamicRenderer classes and adds them to the list box. The application then attempts to create an instance of the GestureRecognizer class and, if successful, adds it to the list box. This fails if the gesture recognizer is not present on the system. Next, the application instantiates a DynamicRenderer object and adds it to the list box. Finally, the application enables each of the plug-ins and the RealTimeStylus object itself.

Another important thing to note about the sample is that in the helper methods, the RealTimeStylus object is first disabled before plug-ins are added or removed and then re-enabled after the addition or removal is complete.

private void RemoveFromPluginCollection(int index)
{
    IStylusSyncPlugin plugin = ((PluginListItem)chklbPlugins.Items[index]).Plugin;

    bool rtsEnabled = myRealTimeStylus.Enabled;
    myRealTimeStylus.Enabled = false;
    myRealTimeStylus.SyncPluginCollection.Remove(plugin);
    myRealTimeStylus.Enabled = rtsEnabled;
}