Monday, June 22, 2009

Custom Ink Control Part II

After completing the previous control, I decided I wanted to extend its functionality, this post will attempt to do that. You will need to complete Custom Ink Control to be able to follow along on this post as it basically just adds on to it.
I want to be able to change the color of the ink used on the control, to do this i will need to add a ColorDialog control to the form. Now create another button on your toolstrip and double-click to open its event. Place the following code after the opening class with the rest of your dimensions:

Dim thisDrawingAttributes As New DrawingAttributes

and this in the button event:

If Me.ColorDialog1.ShowDialog = Windows.Forms.DialogResult.OK Then
thisDrawingAttributes.Color = Me.ColorDialog1.Color
inkOverlay.DefaultDrawingAttributes = thisDrawingAttributes
End If

Drawings from the ink perspective have many attributes and all of them are set by default, since we are changing one of them (color) we need to tell the program to use these new defaults. If not, nothing happens and the pen is the default color. Test it out now.

Next I want to increase/decrease the width of the line, this is also done through DrawingAttributes. At first I started creating a splitbutton with pictures of the lines etc like the one in Word but decided i liked the speed and ease of a button instead, like the font size increase/decrease buttons.

Add 2 more buttons to your toolstrip, one to decrease pen width, one to increase it. You can mess around with pen widths to get the increments that you like, but testing showed not a vast difference unless you made large jumps in width so i put my adjustments in at 25.

For the decrease width event:

If thisDrawingAttributes.Width - 25 > 1 Then
thisDrawingAttributes.Width -= 25
inkOverlay.DefaultDrawingAttributes = thisDrawingAttributes
End If

The first line keeps the code from crashing when going below 1, the rest just decrements the width and makes the change visible.

For the increase width event:


thisDrawingAttributes.Width += 25
inkOverlay.DefaultDrawingAttributes = thisDrawingAttributes


Now just format your buttons and update your ToolTipText.

Yes I am aware that my icons stink, they are just some that I had sitting on the comp, will take icon donations :)
I think this turned out pretty well, the toolbar didn't take up as much space as i thought it might. Let me know in the comments.

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.