hether it's full-text search or geometric queries, you'll have to move beyond BLOBs to get the most from storing Ink in the database. The SQL standard does not provide an Ink storage type, so the question is what shape the underlying database should take to accommodate your needs? This is a vital question due to another important factor: the persistence formats provided by the Tablet PC SDK do not expose any properties because they're totally opaque.
If you've chosen a database rather than a file system for storage, it's probable that you've done so because you're interested in some type of querying, filtering, or association with the Ink, so in most situations, you end up explicitly storing some derived data (its location, the recognized text, the color of the strokes, etc.) alongside the Ink.
On the other hand, creating a normal-form representation of all the data associated with the Ink datatype would be daunting and inefficient (a Point
table joined to a Stroke
table by a many-to-many association table? Good luck with that!). So you face immediate trade-offs. What derived data do you need to store to satisfy your query requirements? Should you store an XML representation of the Stroke in an XML column and, barring XLINQ, end up with an application that combines SQL and XQuery operations? Should you just store the raw representation, re-inflate the Ink on the client-side, and do queries and filters on native Ink objects?
There can be no single correct answer for these questions, although you should bear in mind that Tablet clients often have processor, memory, and power constraints that can make large in-memory working sets problematic.
However unlikely it is that you will store just the Ink, let's leave those issues aside for the first example, and just concentrate on moving Ink in and out of the database. To store Ink, you can use a few different types for your data field. In SQL Server, the best choices for storing a BLOB are varbinary(MAX)
. For no particular reason other than familiarity, I chose an image
In SQL Server, create a database called Ink, a table called Example1, and give the table two fields: Ink of type image
and Length of type int
In Visual Studio 2005, create a new Windows application and drop two buttons onto the resulting Form1. Label them "Save" and "Load." You should see something along the lines of Figure 1
|Figure 1. The figure shows the process of getting ready for some inky database work.|
|Author's Note: Although this article was written based on ADO.NET 2.0 and Visual Studio 2005, the Tablet-specific code is based on the Tablet SDK version 1.7 and can be used with either version of ADO.NET. Obviously, the tools and the database-related class names will be somewhat different, depending on whether you're using the 2003 or 2005 versions of Visual Studio and ADO.NET.
Now add some Ink. The first thing to do is add a reference to the Microsoft Tablet PC API assembly and add a using Microsoft.Ink;
statement to your Using
block at the top of Form1.cs
. With the reference resolved, the next task is to instantiate an InkOverlay object and associate it with a Control.
Because this is playing fast and loose, just associate the InkOverlay with the main form's surface. Add a member called io
of type InkOverlay and a member called isDisposed
of type bool
. Instantiate io
in the Form1()
constructor, passing in the control on which you wish to gather Ink using io = new InkOverlay(this)
and set its Enabled
property to true
. You don't have to explicitly initialize the isDisposed
member because you want its value to be false
, the default for Boolean variables.
|You should not depend on the garbage collector to efficiently recycle ink-collecting objects.|
It's important to know that Ink collection relies on non-managed resources, so you should not depend on the garbage collector to efficiently recycle Ink-collecting objects. That should be done by calling the Dispose()
method of your InkOverlay and using the Dispose(bool)
pattern in your code to control that call. Part of this pattern is that your code must expect Dispose(bool)
to be called multiple times, so the call to io.Dispose()
is placed within a test assuring that the call is safe:
protected override void Dispose(bool disposing)
if (disposing && (components != null))
if (disposing && ! isDisposed && io != null)
isDisposed = true;
Run the program and you should be able to Ink on the main form. Now save the data. Back in Visual Studio, add an event handler to the Save button, and add the code in Listing 1
. This is bare-bones code; in a real application, you'd make the acquisition of a database connection and the creation of the SqlCommand the responsibility of a stand-alone class, but this code shows all the components in a single piece.
The first step is to get the BLOB of Ink data: this is done in the first line of the method with the call to io.Ink.Save(PersistenceFormat.InkSerializedFormat)
. The next chunk of code is the bare basics of getting the data into the database: the instantiation of appropriate ConnectionStringSettings, SqlConnection, SqlCommand, and SqlParameter objects and the use of cmd.ExecuteNonQuery()
to perform the actual insertion.
Finally, after the Ink has been pushed up to the database (or, more accurately, the byte
representing the Ink data), you clear the Ink from the InkOverlay by calling DeleteStrokes()
and triggering a repaint with a call to this.Invalidate()
Now add the code in Listing 2
as an event handler for the Load button. The first section of code is database boilerplate. By the time execution gets to the comment Retrieve Ink bytes, you've advanced the SqlDataReader sdr
to the first row of the database. The three lines following are used to move the data into the inkBytes
variable of type byte
The last task is to load the InkOverlay with the data represented by inkBytes
. There are three rules:
- You cannot programmatically load data into an InkOverlay that is Enabled.
- You cannot load data into a non-empty Ink object.
- Mistakes with Ink sometimes throw cryptic exceptions and sometimes just silently fail, so follow the other two rules.
At this point, Example1 should give you what you need to store and load Ink from the database, except that no matter much data you store in the database, you'll always retrieve the first record. Let's fix that.