Monday, June 22, 2009

Custom Ink Control

This post will hopefully show how to create a custom ink input control for a tablet PC. Don't worry if you don't know how to build a custom control, this is going to show you everything. I don't know at this point what it will include, but i'm tired of re-creating ink controls over and over again. So this will be a base control to use in other projects. Things I know will be implemented are:

Text only recognition
Number only recognition
Clear
Save


I will attempt to create all of this with buttons rather than gesture control as gestures can sometimes get in the way what you are writing.

First things first, I am writing this in .NET Express so anyone can follow along. To get a custom control in Express, create a Class Library project, then right click and delete the Class1 from the Solution Explorer. Next, right click your solution and Add->User Control and give it a good name. You should now have a blank user control template like this one:


Now, add a split container control and set it's orientation to horizontal. Drag a panel control into the bottom pane of the split container and set its dock property to fill. Now give the panel a light background color that will clue the user in as to where ink will be accepted. I am using Inactive Caption Text under the System tab.

Now add a textbox to the top pane and change its dock property to fill, set the font to how you would like the data displayed (i used MS San Serif 16), and then move the split containers' split up to just under the textbox to maximize our ink surface. You should now have a layout similar to this:




Now for some basic code. I will not go too much into detail as to what each and every line does if the code already is explained in depth in earlier tablet programming posts here, if the code is new to this post, i will detail it.

First, add a reference to Microsoft Tablet PC API, its under the .NET tab near the top. Next place this statement at the top before your class:

Imports Microsoft.Ink

Inside the class place this code


Private WithEvents inkOverlay As New InkOverlay
Dim oRecognizers As New Recognizers()
Dim oReco As Recognizer = oRecognizers.GetDefaultRecognizer
Dim oRC As RecognizerContext = oReco.CreateRecognizerContext

And in the load event place this:

InkOverlay.Handle = Me.Panel1.Handle
InkOverlay.Enabled = True

Now create an InkOverlay Stroke event and place this inside:

oRC.Strokes = inkOverlay.Ink.Strokes
Dim cnt = oRC.Strokes.Count
oRC.EndInkInput()
Dim iStat As RecognitionStatus
Dim oResult As RecognitionResult
If cnt = 0 Then Me.TextBox1.Clear()
If cnt <> 0 Then
oResult = oRC.Recognize(iStat)
Me.TextBox1.Text = oResult.TopAlternate.ToString
End If

This will count the strokes as you make them and input what it interprets the input to be into the textbox. Run it and give it a try.

Next I want to create some buttons to extend the functionality of the control. Though buttons take up ink real estate, they are much more intuitive than gesture controls (people aren't good at remembering which gestures do what). I added a Toolstrip control and set its dock to bottom, set the Gripstyle to hidden, and the BackColor to ActiveBorder. I changed the BackColor because I want users to associate the pale blue of the panel as an ink surface so it will be intuitive to them where to write. You can use another color, the same color, or even use a background image to make the toolstrip look better. Speaking of looks, lets format a little bit real quick. I select the SplitContainer and set the borderstyle to FixedSingle, set IsSplitterFixed to true, and the SplitterWidth to 1.

Ok, our first button will be used to clear all ink from the control, to do this we need to use a custom subroutine and call it under the button click event:

Sub ClearInk()
On Error Resume Next 'you can handle errors later in your own way
InkOverlay.Enabled = False
InkOverlay.Ink.DeleteStrokes(InkOverlay.Ink.Strokes)
Panel1.Invalidate()
inkOverlay.Enabled = True
TextBox1.Clear()
End Sub

Double-click the button to create the event and type:

ClearInk()

Now the button should clear all ink from the control, give it a try. Now click on the button and set its ToolTipText (say that 5 times fast) to Clear, now when they hover over the button they can see what it does. You can also change the button image to something better, it shouldn't be hard as the stock image is horrible.

Now the easy stuff is done and we need to get into properties before we can set up the save functionality. Lets start by creating a property for the data output, this will be a read only property because there is no reason to need to be able to set the text from outside of the control that i can think of, if you can think of a reason then go ahead.

After you type the first line and hit enter, the structure of the property will be filled in for you, you just need to fill in what to return:

ReadOnly Property Output() As String
Get
Return Me.TextBox1.Text
End Get
End Property

To see how this works, run the project, write some sample text and at the bottom of the properties window to the right you should see Output under Misc. and your sample data in the bax next to it. This value is what will be returned when you type controlname.output once this control is in your application.

Now we want to create a way to save the .ISF file (for more info on this look at the previous tablet programming posts). Since we don't know where the user will want to save the file, we will create a default save path that can then be exposed using a custom property. The first step is to import :

Imports System.IO

Just above or below Imports Microsoft.Ink

Now we want to create a couple of strings to hold our default values (you can elect not to have default values, but then you have to remember to do error handling in your form, I would rather the control not cause problems once in the solution), so dimension these just below your opening class statement:

Dim defaultPath As String = "C:\Test\"
Dim defaultName As String = "Sample.isf"

Now the control will work even if they forget to change these. Next lets create the properties that will allow these defaults to be changed:

Property SavePath() As String
Get
Return defaultPath
End Get
Set(ByVal value As String)
defaultPath = value
End Set
End Property

Property SaveName() As String
Get
Return defaultName
End Get
Set(ByVal value As String)
defaultName = value
End Set
End Property

Those will allow you to enter your own location and names at design time, or to pass them in at run time. Next is the simple procedure to save the file:

Sub saveInk()
Dim inkBytes As Byte() = Me.inkOverlay.Ink.Save()
Dim strm As FileStream = File.Open(Me.SavePath & SaveName, FileMode.Create)
strm.Write(inkBytes, 0, inkBytes.Length)
strm.Close()
End Sub

Now create a new button on the toolstrip, double click it and place the following code:

saveInk()

You can now format the button to look the way you want as well as changing the ToolTipText to Save.

Run it and see how it works for you. Look down at the bottom right to see your new properties, notice that you can change the newest pair because we did not make them read-only. Try changing the file name or path name and clicking the save button again.

Lets quickly build a test form to try it out in context. Go to File->Add->New Project and select Windows Application. You should now see a new Form1.VB in your solution explorer. Go to Project->Properties and under application ensure that the Startup Form is Form1, this may require a change in the Apllication Type dropdown. Choose Build->Build Solution and then save everything. Now your control shouold show up automatically in the toolbox (icon looks like a gear) if it does not, right click the toolbox, select Choose Items, click Browse and browse to the file you just saved and in the bin folder under default you should see a .DLL file with the name of your control, double click it and select ok. The control should now be in your toolbox, drag it onto the form as you would any other control.

I am also going to drag a button and 3 textboxes to test my properties. Now right-click the WindowsApplication1 (or whatever you named it) project you just created in the solution explorer and select Set As Startup Project. This will make the for load when you hit run instead of the control, change this back to the control project as needed. Double-click your test button and place the following code:

Me.TextBox1.Text = Me.InkInputControl1.SavePath
Me.TextBox2.Text = Me.InkInputControl1.SaveName
Me.TextBox3.Text = Me.InkInputControl1.Output

Note that your control may not be named the same as mine so adjust accordingly. Run the program and click the button, the textboxes should populate with your properties. Here is what mine looks like:


That is it! The control is ready to go and you know how to create your own properties to control anything you want. Look for an upcoming post on recognizers for my follow-up showing how to look for text or numeric only input.

As always if you have any comments or questions, thoughts or ideas, post em in the comments.

No comments: