RealTimeStylus Ink Collection Sample

This application demonstrates ink collection and rendering when using the RealTimeStylus class.

The InkCollection Project

This sample consists of a single solution that contains one project, InkCollection. The application defines the InkCollection namespace that contains a single class, also called InkCollection. The class inherits from the Form class and implements the IStylusAsyncPlugin interface.

namespace InkCollection
{
    public class InkCollection : Form, IStylusAsyncPlugin
    {
        //...
      

The InkCollection Class defines a set of private constants used to specify various ink thickness. The class also declares private instances of the RealTimeStylus class, myRealTimeStylus, the DynamicRenderer class, myDynamicRenderer, and the Renderer class myRenderer. The DynamicRenderer renders the Stroke that is currently being collected. The Renderer object, myRenderer, renders Stroke objects that have already been collected.

private const float ThinInkWidth = 10;
private const float MediumInkWidth = 100;
private const float ThickInkWidth = 200;

private RealTimeStylus myRealTimeStylus;

private DynamicRenderer myDynamicRenderer;
private Renderer myRenderer;

The class also declares a Hashtable object, myPackets, which is used to store packet data that is being collected by one or more Cursor objects. The Stylus object's Id values are used as the hashtable key to uniquely identify the packet data collected for a given Cursor object.

A private instance of the Ink object, myInk, stores Stroke objects collected by myRealTimeStylus.

private Hashtable myPackets;
        
private Ink myInk;

The Form Load Event

In the Load event handler for the form, myDynamicRenderer is instantiated by using the DynamicRenderer that takes a control as an argument, and myRenderer is constructed with a no-argument constructor.

private void InkCollection_Load(object sender, System.EventArgs e)
{
    myDynamicRenderer = new DynamicRenderer(this);
    myRenderer = new Renderer();
    // ...

Pay attention to the comment that follows the instantiation of the renderers, because myDynamicRenderer uses the default values for DrawingAttributes when rendering the ink. This is standard behavior. However, if you wish to give the ink rendered by myDynamicRenderer a different look from ink rendered by myRenderer, you can change the DrawingAttributes property on myDynamicRenderer. To do so, uncomment the following lines before you build and run the application.

    // myDynamicRenderer.DrawingAttributes.PenTip = PenTip.Rectangle;
    // myDynamicRenderer.DrawingAttributes.Height = (.5F)*MediumInkWidth;
    // myDynamicRenderer.DrawingAttributes.Transparency = 128;

Next, the application creates the RealTimeStylus object that is used to receive stylus notifications and adds the DynamicRenderer object to the synchronous plug-in notification queue. Specifically, myRealTimeStylus adds myDynamicRenderer to the SyncPluginCollection property.

    myRealTimeStylus = new RealTimeStylus(this, true);

    myRealTimeStylus.SyncPluginCollection.Add(myDynamicRenderer);

The form is then added to the asynchronous plug-in notification queue. Specifically, InkCollection is added to the AsyncPluginCollection property. Finally, myRealTimeStylus and myDynamicRenderer are enabled, and myPackets and myInk are instantiated.

    myRealTimeStylus.AsyncPluginCollection.Add(this);

    myRealTimeStylus.Enabled = true;
    myDynamicRenderer.Enabled = true;  
      
    myPackets = new Hashtable();
    myInk = new Ink();
}

Aside from hooking up the menu handlers for changing ink color and size, one more brief block of code is required before implementing the interface. The sample must handle the form's Paint event. In the event handler, the application must refresh myDynamicRenderer because it is possible that a Stroke object is being collected at the time the Paint event occurs. In that case, the portion of the Stroke object that has already been collected needs to be redrawn. The static Renderer is used to re-draw Stroke objects that have already been collected. These strokes are in the Ink object because they are placed there when drawn, as shown in the next section.

private void InkCollection_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
    myDynamicRenderer.Refresh();

    myRenderer.Draw(e.Graphics, myInk.Strokes);
} 

Implementing the IStylusAsyncPlugin Interface

The sample application defines the types of notifications it has interest in receiving in the implementation of the DataInterest property. The DataInterest property therefore defines which notifications that the RealTimeStylus object forwards to the form. For this sample, the DataInterest property defines interest in the StylusDown, Packets, StylusUp, and Error notifications through the DataInterestMask enumeration.

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

The StylusDown notification occurs when the pen touches the digitizer surface. When this happens, the sample allocates an array that is used to store the packet data for the Stylus object. The StylusDownData from the StylusDown method is added to the array, and the array is inserted into the hashtable by using the Stylus object's Id property as a key.

public void StylusDown(RealTimeStylus sender, StylusDownData data)
{
    ArrayList collectedPackets = new ArrayList();

    collectedPackets.AddRange(data.GetData());

    myPackets.Add(data.Stylus.Id, collectedPackets);
}

The Packets notification occurs when the pen moves on the digitizer surface. When this occurs, the application adds new StylusDownData into the packet array for the Stylus object. It does this by using the Stylus object's Id property as the key to retrieve the packet array for the stylus from the hashtable. The new packet data is then inserted into the retrieved array.

public void Packets(RealTimeStylus sender, PacketsData data)
{
    ((ArrayList)(myPackets[data.Stylus.Id])).AddRange(data.GetData());
}

The StylusUp notification occurs when the pen leaves the digitizer surface. When this notification occurs, the sample retrieves the packet array for this Stylus object from the hashtable-removing it from the hashtable as it is no longer needed, adds in the new packet data, and uses the array of packet data to create a new Stroke object, stroke.

public void StylusUp(RealTimeStylus sender, StylusUpData data)
{
    ArrayList collectedPackets = (ArrayList)myPackets[data.Stylus.Id];
    myPackets.Remove(data.Stylus.Id);

    collectedPackets.AddRange(data.GetData());

    int[] packets = (int[])(collectedPackets.ToArray(typeof(int)));
    TabletPropertyDescriptionCollection tabletProperties =
        myRealTimeStylus.GetTabletPropertyDescriptionCollection(data.Stylus.TabletContextId);

    Stroke stroke = myInk.CreateStroke(packets, tabletProperties);
    if (stroke != null) 
    {
         stroke.DrawingAttributes.Color = myDynamicRenderer.DrawingAttributes.Color;
         stroke.DrawingAttributes.Width = myDynamicRenderer.DrawingAttributes.Width;
    } 
}

For an example that shows more robust use of the RealTimeStylus class, including the use of custom plug-in creation, see RealTimeStylus Plug-in Sample.

Microsoft.Ink.Renderer

Microsoft.StylusInput.DynamicRenderer

Microsoft.StylusInput.RealTimeStylus

Microsoft.StylusInput.IStylusAsyncPlugin

Accessing and Manipulating Stylus Input