HomeDigital EditionSearch Dotnet Cd
ASP.NET C# Certification Exams The CLI Data Access Editorials Extending .NET Fundamentals Interoperability Interviews Migrate Mobile .NET Mono .NET Interface Object-Oriented Programming Open Source Optimization Product/Book Reviews Security Source Code UML Visual Studio .NET

Microsoft has a rich history of development environments and platforms built around the creation of reusable objects and components to maximize developer productivity. These environments are designed to mask some of the underlying complexities of the platform. The addition of the Tablet PC SDK to Visual Studio .NET follows this pattern.

In previous articles I touched on several of the basic features of the Tablet PC object model. In this article I take a look at a few of the basic steps needed to create an ink- enabled custom control. Additionally, I look at some of the technologies discussed in previous articles with an eye toward creating a reusable component and object.

As the Tablet PC space matures, independent component publishers are creating new and exciting ink-enabled controls. However, the platform still enables developers to create their own, if desired.

Windows XP Tablet Edition is a superset of the Windows XP operating system; because of this developers can use all of the features of Windows Forms and controls to create ink-enabled applications. Start by creating a new C# Windows Control project named InkInputControl. In order to access the ink libraries you need to set a reference to the Microsoft.Ink assembly, located by default at C:\Program Files\Microsoft Tablet PC Platform SDK\Include\Microsoft.Ink.dll.

At the heart of ink enablement is the InkCollector object, which handles all of the ink capture for the application. In order to make a surface of an application inkable, simply create a new InkCollector and pass it a handle to the object you would like to ink-enable. In the following code snippet, the local InkCollector is initialized and passes a handle to the control container. This will allow the entire container to be inkable. After initializing the InkCollector, the object is enabled to begin receiving ink.

this.inkCollector = new InkCollector(this.Handle);
this.inkCollector.Enabled = true;

Once the InkCollector is associated with the control container the control moves on to initialize the pen characteristics. While the InkCollector initializes all of these attributes to standard defaults, the InkInputControl adjusts these to its own internal default settings. The default drawing characteristics of the ink are controlled by the DefaultDrawingAttributes property of the InkCollector.

The InkInputControl allows the consumer of the control to set three pen-related attributes from publicly exposed properties, the pen color, pen width, and the type of tip on the pen (see Listing 1). These three properties then configure the InkCollector.

The first of these pen-related properties is PenColor. The PenColor property works by taking in a standard .NET color object and passing it through to the Color attribute of the DefaultDrawingAttributes property.

The PenTip property uses the PenTip enumeration that is found in the Microsoft.Ink namespace. There are currently two supported pen tips, ball and rectangle.

The size of these tips is adjusted by the PenWidth property. This property is a floating-point number that describes the width of the pen tip. Since pressure sensitivity is enabled on the InkCollector, by default this width is relative. If a user applies maximum pressure with the pen, the actual width of the ink will be one and half times the width of the tip. If the user applies the minimal pressure on the pen, this value will be one-half of the width of the tip. A developer is able to turn off pressure sensitivity by setting the IgnorePressure attribute on the DefaultDrawingAttributes property to true.

Now that the default properties are initialized, the InkInputControl is ready to accept ink. However, in order to make this a particle control, some events and methods need to be added.

The InkInputControl requires an event to communicate with an application notifying it when a user completes writing and the text is recognized.

public event StripEventHandler InkRecognized;
public delegate void StripEventHandler(object sender, InkInputEventArgs e);

The InkInputControl will raise an Inkrecognized event based on a preset timer when the user has completed writing. The event will pass an InkInputEventArgs parameter to the event containing the recognized text and the strokes collection.

In order to figure out exactly when the user finishes, the InkInputControl uses a timer set to a predetermined default. Using the CursorButtonDown event of the InkCollector we can determine when the user starts to write on the control. The CursorButtonDown event is fired when the user has the pen on the digitizer and is actively writing a stroke. The timer is stopped when the InkInputControl receives this event from the InkCollector.

After a user completes a stroke and lifts the pen from the screen, the CursorButtonUp event is raised by the InkCollector. Upon receiving this event the InkInputControl resets the timer and starts it running.

If no new strokes are detected before the timer expires, the control's InkRecognized event is raised. This notifies the application that the user has completed writing and the InkInputPanel has recognized the text.

The conversion of the strokes into text (see Figure 1) is handled by the handwriting recognizers installed with the Windows XP Tablet PC Edition operating system. Developers can access the properties and methods of the recognizer object to convert the handwriting into text, but the InkInputControl uses a well-placed shortcut to access the default recognizer. The InkInputControl calls the ToString method of the Strokes collection. When called, the ToString method will invoke the default recognizer for the machine and return the text equivalent for the strokes.

For maximum flexibility the control also returns the entire strokes collection as part of the event argument passed to the InkRecognized event.

Now the InkInputControl has a fully inkable surface, properties that allow the user to change common pen characteristics, and an event that notifies the consumer of the control that the user has finished inking and that the text is recognized. One last method is needed to add basic functionality to the control: the ability to clear the ink from the writing surface and start over.

To implement the clear functionality the InkInputControl uses the DeleteStrokes method of the Ink object to clear all of the strokes. The DeleteStrokes method clears all of the strokes from the collection if a specific collection of strokes to delete is not passed to the method.

public void Clear()
{
if (! this.inkCollector.CollectingInk)
{
this.inkCollector.Ink.DeleteStrokes();
Refresh();
}
}

After checking if the InkCollector is actively collecting ink from the user, the Clear method deletes all of the strokes and redraws the control.

Now we will make a change to our object. Instead of the InkCollector, we will change to the InkOverlay object for the foundation of the control.

The InkOverlay object is a superset of the InkCollector object, providing editing support. It is useful for applications in which one wishes to work with ink that is neither handwriting nor gestures.

Because the InkOverlay is a drop-in replacement, all of the references to InkCollector can globally be replaced with InkOverlay and the control can be recompiled.

this.inkOverlay = new InkOverlay(this.Handle);
this.inkOverlay.Enabled = true;

The local InkOverlay is initialized and passes a handle to the control container. This will allow the entire container to be inkable. After initializing the InkCollector the object is enabled to begin receiving ink.

One of the differences with the InkOverlay control is that it provides better support for erasing. Take advantage of the InkInputControl by creating and exposing two new properties to control the editing mode and type of eraser to be used (see Listing 2). The EditingMode and EraserMode offer consumers of the InkInputControl the ability to erase and modify any text.

The InkOverlayEditingMode enumeration allows us to toggle the control between ink modes, in which users can write with the pen tip; erase mode, in which users can correct mistakes; and select mode, which allows users to lasso sections of ink to perform various operations.

Erasing is completed in various ways based on the form factor of the Tablet PC. Some tablets, such as the Toshiba Portege, have an "eraser" button on the back end of the pen, like a real pencil. Other tablet manufacturers do not include this additional button on their machines.

To allow for users without the "eraser" on the top of their pen, consumers of this InkInputControl will need to implement a button in their user interface that sets the EditingMode property of the control. This allows the end user to toggle between erase and ink mode.

Both the InkOverlay and the InkCollector raise a NewInAirPacket event. This event, along with the NewPacket event, allows developers to track the movement of the mouse or pen across the screen. The difference between the NewPacket event and the InAir event is that the NewPacket event is raised when the pen tip is pressed on the screen, but the NewInAirPacket event is raised as the user moves the pen above the screen.

The InkInputControl captures the NewInAirPackets event and evaluates the InkCollectorNewInAirPacketsEventArgs object passed to it. This object contains a reference to the Cursor object. The Cursor object exposes an Inverted property that will tell the application if the pen is flipped or right side up (see Listing 3).

The NewInAirPackets event is used to capture the movement of the pen across the screen. If it detects that the pen is inverted the control is put into erase mode, which enables a user to simply press the top of the pen to the screen and erase a section of line. The control defaults to point-erase mode, mimicking the standard action of Microsoft Journal. This can be changed by the developer, but care should be taken if the end users are familiar and comfortable with the Journal interface.

If the user flips the pen back so that the tip is down, the control will automatically revert to ink mode. For controls or an application in which a user can toggle between ink and erase modes with a button or by flipping the pen, developers should implement code to make sure these two methods do not interfere with each other.

Now that users of the InkInputControl have the ability to control the pen settings, utilize text recognition, erase with either a button or with the pen tip, and clear the ink, the next thing to add is the ability to persist the ink to a file.

The Tablet PC managed libraries provide the ability to persist ink to either a fortified GIF or to the native Ink Serialized Format (ISF).

Ink Serialized Format is the most compact of the various formats. In addition, ISF can be embedded within a binary document or moved directly to and from the Clipboard. Developers may use a Base-64 encoded ISF format to encode ISF for use directly in XML or HTML files.

The other option is a new GIF format called a fortified GIF. A fortified GIF is a GIF file that contains ISF information as additional metadata with the file. This additional metadata is ignored by applications that do not understand ink, so it is the prime choice for sharing ink-annotated images and ink files. When the file is returned to an application that does understand ink, all of the ink data is maintained and restored.

The InkInputControl implements a Save method that allows consuming applications to persist the ink to a file. Because the image can be used by any application, (for example, mailed by a user to a co-worker with Outlook), the InkInputControl persists the file as a fortified GIF (see Listing 4).

Conclusion
Because of the robust nature of the managed libraries for the Tablet PC, the InkInputControl is able to add support for ink, erase, and selection modes with a few lines of code and by using the drop-in replacement for the InkCollector, the InkOverlay. In addition, by using the NewInAirPackets event the control is able to easily tell if the user is using the pen with the point down or the top down in an erase mode. Utilizing the Save method of the ink collection developers can easily persist ink to files in the universally readable format of a GIF or the native ISF.

With very little code we now have the makings of a full-featured Microsoft Journal-like control. The control can be farther enhanced to add toolbar support, thus providing a built-in interface and other more advanced features such as zooming and cut and paste, by extending the public properties and methods. This additional functionality is easy to implement with the Tablet PC SDK.

About The Author
Brad McCabe is a technology evangelist for .NET, ASP.NET, and .NETCF for Infragistics (www.infragistics.com), a leader in providing a broad infrastructure of reusable presentation-layer components essential for the creation of next-generation Web-based applications and XML Web services utilizing .NET, COM, and Java. Brad@infragistics.com




Listing 1

public Color PenColor
		{
	get {return
	this.inkCollector.DefaultDrawingAttributes.Color;}
	set {this.inkCollector.DefaultDrawingAttributes.Color = value;}
		}

		public float PenWidth
		{
	get {return
	this.inkCollector.DefaultDrawingAttributes.Width;}
	set {this.inkCollector.DefaultDrawingAttributes.Width = value;}
		}

		public PenTip PenTip
		{
	get {return this.inkCollector.DefaultDrawingAttributes.PenTip;}
	set {this.inkCollector.DefaultDrawingAttributes.PenTip = value;}
		}







Listing 2

public InkOverlayEditingMode EditingMode
{
	get {return this.inkOverlay.EditingMode;}
	set {this.inkOverlay.EditingMode = value;}
}

public InkOverlayEraserMode EraserMode
{
get {return this.inkOverlay.EraserMode;}
	set {this.inkOverlay.EraserMode = value;}
}






Listing 3

private void OnInkOverlayNewInAirPackets(object sender,
 InkCollectorNewInAirPacketsEventArgs e)
{
	if (e.Cursor.Inverted)
	{
		this.EditingMode =
		Microsoft.Ink.InkOverlayEditingMode.Delete;
		this.EraserMode =
		Microsoft.Ink.InkOverlayEraserMode.PointErase;
	} else {
		this.EditingMode = Microsoft.Ink.InkOverlayEditingMode.Ink;
	}
}





Listing 4

		public void Save()
{
	SaveFileDialog saveDialog = new SaveFileDialog();
	saveDialog.Filter = "Image file (*.gif)|*.gif";

	if(saveDialog.ShowDialog() == DialogResult.OK)
	{
		byte[] fortifiedGif = null;

		UTF8Encoding utf8 = new UTF8Encoding();

		using (FileStream File =
		File.OpenWrite(saveDialog.FileName))
		{
			fortifiedGif =
			inkOverlay.Ink.Save(PersistenceFormat.Gif);

			File.Write(fortifiedGif, 0, fortifiedGif.Length);
		}
	}
}

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.

  E-mail: info@sys-con.com