The Ink Object Model: Windows Forms and COM versus WPF

There are essentially three platforms that support digital ink: the Tablet PC Windows Forms platform, the Tablet PC COM platform, and the Windows Presentation Foundation (WPF) platform. The Windows Forms and COM platforms share a similar object model, but the object model for the WPF platform is substantially different. This topic discusses the differences at a high-level so that developers that have worked with one object model can better understand the other.

Enabling Ink in an Application

All three platforms ship objects and controls that enable an application to receive input from a tablet pen. The Windows Forms and COM platforms ship with InkPicture, InkEdit, InkOverlay and InkCollector classes. InkPicture and InkEdit are controls that you can add to an application to collect ink. The InkOverlay and InkCollector can be attached to an existing window to ink-enable windows and custom controls.

The WPF platform includes the InkCanvas control. You can add an InkCanvas to your application and begin collecting ink immediately. With the InkCanvas, the user can copy, select, and resize ink. You can add other controls to the InkCanvas, and the user can handwrite over those controls, too. You can create an ink-enabled custom control by adding an InkPresenter to it and collecting its stylus points.

The following table lists where to learn more about enabling ink in an application:

To do this…

On the WPF Platform…

On the Windows Forms/COM Platforms…

Add an ink-enabled control to an application

See Getting Started with Ink.

See Auto Claims Form Sample

Enable ink on a custom control

See Creating an Ink Input Control.

See Ink Clipboard Sample.

Ink Data

On the Windows Forms and COM platforms, InkCollector, InkOverlay, InkEdit, and InkPicture each expose a Ink object. The Ink object contains the data for one or more Stroke objects and exposes common methods and properties to manage and manipulate those strokes. The Ink object manages the lifetime of the strokes it contains; the Ink object creates and deletes the strokes that it owns. Each Stroke has an identifier that is unique within its parent Ink object.

On the WPF platform, the System.Windows.Ink.Stroke class owns and manages its own lifetime. A group of Stroke objects can be collected together in a StrokeCollection, which provides methods for common ink data management operations such as hit testing, erasing, transforming, and serializing the ink. A Stroke can belong to zero, one, or more StrokeCollection objects at any give time. Instead of having a Ink object, the InkCanvas and InkPresenter contain a System.Windows.Ink.StrokeCollection.

The following pair of illustrations compares the ink data object models. On the Windows Forms and COM platforms, the Ink object constrains the lifetime of the Stroke objects, and the stylus packets belong to the individual strokes. Two or more strokes can reference the same DrawingAttributes object, as shown in the following illustration.

Diagram of the Ink Object Model for COM/Winforms.

On the WPF, each System.Windows.Ink.Stroke is a common language runtime object that exists as long as something has a reference to it. Each Stroke references a StylusPointCollection and System.Windows.Ink.DrawingAttributes object, which are also common language runtime objects.

Diagram of the Ink Object Model for WPF.

The following table compares how to accomplish some common tasks on the WPF platform and the Windows Forms and COM platforms.

Task

Windows Presentation Foundation

Windows Forms and COM

Save Ink

Save

Save()

Load Ink

Create a StrokeCollection with the StrokeCollection.StrokeCollection(Stream) constructor.

Load(Byte[])

Hit test

HitTest

HitTest()

Copy Ink

CopySelection

ClipboardCopy()

Paste Ink

Paste

ClipboardPaste()

Access custom properties on a collection of strokes

AddPropertyData (the properties are stored internally and accessed via AddPropertyData, RemovePropertyData, and ContainsPropertyData)

Use ExtendedProperties()

Sharing ink between platforms

Although the platforms have different object models for the ink data, sharing the data between the platforms is very easy. The following examples save ink from a Windows Forms application and load the ink into a Windows Presentation Foundation application.

Imports Microsoft.Ink
Imports System.Drawing


...


'/ <summary>
'/ Saves the digital ink from a Windows Forms application.
'/ </summary>
'/ <param name="inkToSave">An Ink object that contains the 
'/ digital ink.</param>
'/ <returns>A MemoryStream containing the digital ink.</returns>
Function SaveInkInWinforms(ByVal inkToSave As Ink) As MemoryStream 
    Dim savedInk As Byte() = inkToSave.Save()

    Return New MemoryStream(savedInk)

End Function 'SaveInkInWinforms
using Microsoft.Ink;
using System.Drawing;


...


/// <summary>
/// Saves the digital ink from a Windows Forms application.
/// </summary>
/// <param name="inkToSave">An Ink object that contains the 
/// digital ink.</param>
/// <returns>A MemoryStream containing the digital ink.</returns>
MemoryStream SaveInkInWinforms(Ink inkToSave)
{
    byte[] savedInk = inkToSave.Save();

    return (new MemoryStream(savedInk));

}
Imports System.Windows.Ink



...


'/ <summary>
'/ Loads digital ink into a StrokeCollection, which can be 
'/ used by a WPF application.
'/ </summary>
'/ <param name="savedInk">A MemoryStream containing the digital ink.</param>
Public Sub LoadInkInWPF(ByVal inkStream As MemoryStream) 
    strokes = New StrokeCollection(inkStream)

End Sub 'LoadInkInWPF

using System.Windows.Ink;


...


/// <summary>
/// Loads digital ink into a StrokeCollection, which can be 
/// used by a WPF application.
/// </summary>
/// <param name="savedInk">A MemoryStream containing the digital ink.</param>
public void LoadInkInWPF(MemoryStream inkStream)
{
    strokes = new StrokeCollection(inkStream);
}

The following examples save ink from a Windows Presentation Foundation application and load the ink into a Windows Forms application.

Imports System.Windows.Ink



...


'/ <summary>
'/ Saves the digital ink from a WPF application.
'/ </summary>
'/ <param name="inkToSave">A StrokeCollection that contains the 
'/ digital ink.</param>
'/ <returns>A MemoryStream containing the digital ink.</returns>
Function SaveInkInWPF(ByVal strokesToSave As StrokeCollection) As MemoryStream 
    Dim savedInk As New MemoryStream()

    strokesToSave.Save(savedInk)

    Return savedInk

End Function 'SaveInkInWPF

using System.Windows.Ink;


...


/// <summary>
/// Saves the digital ink from a WPF application.
/// </summary>
/// <param name="inkToSave">A StrokeCollection that contains the 
/// digital ink.</param>
/// <returns>A MemoryStream containing the digital ink.</returns>
MemoryStream SaveInkInWPF(StrokeCollection strokesToSave)
{
    MemoryStream savedInk = new MemoryStream();

    strokesToSave.Save(savedInk);

    return savedInk;
}
Imports Microsoft.Ink
Imports System.Drawing


...


'/ <summary>
'/ Loads digital ink into a Windows Forms application.
'/ </summary>
'/ <param name="savedInk">A MemoryStream containing the digital ink.</param>
Public Sub LoadInkInWinforms(ByVal savedInk As MemoryStream) 
    theInk = New Ink()
    theInk.Load(savedInk.ToArray())

End Sub 'LoadInkInWinforms
using Microsoft.Ink;
using System.Drawing;


...


/// <summary>
/// Loads digital ink into a Windows Forms application.
/// </summary>
/// <param name="savedInk">A MemoryStream containing the digital ink.</param>
public void LoadInkInWinforms(MemoryStream savedInk)
{
    theInk = new Ink();
    theInk.Load(savedInk.ToArray());
}

Events from the Tablet Pen

The InkOverlay, InkCollector, and InkPicture on the Windows Forms and COM platforms receive events when the user inputs pen data. The InkOverlay or InkCollector is attached to a window or a control, and can subscribe to the events raised by the tablet input data. The thread on which these events occurs depends on whether the events are raised with a pen, a mouse, or programmatically. For more information about threading in relation to these events, see General Threading Considerations and Threads on Which an Event Can Fire.

On the Windows Presentation Foundation platform, the UIElement class has events for pen input. This means that every control exposes the full set of stylus events. The stylus events have tunneling/bubbling event pairs and always occur on the application thread. For more information, see Routed Events Overview.

The following diagram shows compares the object models for the classes that raise stylus events. The Windows Presentation Foundation object model shows only the bubbling events, not the tunneling event counterparts.

Diagram of the Stylus events in WPF vs Winforms.

Pen Data

All three platforms provide you with ways to intercept and manipulate the data that comes in from a tablet pen. On the Windows Forms and COM Platforms, this is achieved by creating a RealTimeStylus, attaching a window or control to it, and creating a class that implements the IStylusSyncPlugin or IStylusAsyncPlugin interface. The custom plug-in is then added to the plug-in collection of the RealTimeStylus. For more information about this object model, see Architecture of the StylusInput APIs.

On the WPF platform, the UIElement class exposes a collection of plug-ins, similar in design to the RealTimeStylus. To intercept pen data, create a class that inherits from StylusPlugIn and add the object to the StylusPlugIns collection of the UIElement. For more information about this interaction, see Intercepting Input from the Stylus.

On all platforms, a thread pool receives the ink data via stylus events and sends it to the application thread. For more information about threading on the COM and Windows Platforms, see Threading Considerations for the StylusInput APIs. For more information about threading on the Windows Presentation Software, see The Ink Threading Model.

The following illustration compares the object models for the classes that receive pen data on the pen thread pool.

Diagram of the StylusPlugin model WPF vs Winforms.