InkEdit

InkEdit

The InkEdit control is a superset of standard text boxes in Windows. As such, it is meant to replace edit controls in an ink-aware application. Instead of accepting only text input, InkEdit also captures digital ink, optionally converting it into text. The managed version of InkEdit is a superset of RichTextBox, while the ActiveX and Win32 versions are supersets of RichEdit. RichEdit and its .NET counterpart, RichTextBox, supply advanced text-editing features for text boxes in Windows. These features include support for entering complex scripts, saving in Rich Text Format (RTF), and editing bidirectional text. Perhaps the most notable exposure of RichEdit is in Windows WordPad, whose text features are all implemented by its underlying RichEdit control.

InkEdit offers a superset of RichEdit functionality, which immediately makes it much more than just a simple digital ink capture and display control. Through InkEdit, you can access all the functionality of RichEdit. However, in this chapter we will be focusing only on the new features that InkEdit adds. For more information about the RichEdit and RichTextBox functionality, please see Programming Microsoft Windows with C#, by Charles Petzold (Microsoft Press, 2002).

InkEdit Basics

The Tablet PC team designed InkEdit with pen-based data entry scenarios in mind. Applications that require the user to enter text by means of text boxes can use an InkEdit control to make data entry a seamless experience for the user. Instead of having to bring up the Tablet PC Input Panel whenever entering text, the user can simply write directly into an InkEdit control, which takes care of converting the written ink into text.

The sample application that follows shows how you can integrate InkEdit into a Windows Forms application. This form gathers basic information to register college students, requiring that students enter their name, identification number, and phone number, and allowing them to add optional comments. It uses InkEdit controls without any customization, preferring the default behavior that s supplied by the controls. This means that on a non-tablet Windows machine, you won t get an inking cursor. The application is shown running in Figure 8-1.

figure 8-1 the collegebasic application running with both ink and text in its inkedit fields.

Figure 8-1. The CollegeBasic application running with both ink and text in its InkEdit fields.

CollegeBasic.cs

//////////////////////////////////////////////////////////////////// // CollegeBasic.cs // (c) 2002 Microsoft Press, by Philip Su // This program demonstrates basic usage of InkEdit controls //////////////////////////////////////////////////////////////////// using System; using System.Drawing; using System.Windows.Forms; using Microsoft.Ink; public class CollegeBasic : Form {     private InkEdit m_editFirst;    // First name     private InkEdit m_editLast;     // Last name     private InkEdit m_editID;       // Student ID     private InkEdit m_editPhone;    // Phone number     private InkEdit m_editComment;  // Any comments     private Label m_labelFirst;     private Label m_labelLast;     private Label m_labelID;     private Label m_labelPhone;     private Label m_labelComments;     [STAThread]     static void Main()      {         Application.Run(new CollegeBasic());     }     public CollegeBasic()     {         InitializeComponent();     }     private void InitializeComponent()     {         SuspendLayout();             // Create the InkEdit controls and labels         InitializeEdit(ref m_editFirst, new Point(80, 24),                         new Size(160, 32), false);         InitializeEdit(ref m_editLast, new Point(344, 24),                         new Size(160, 32), false);         InitializeEdit(ref m_editID, new Point(80, 64),                         new Size(160, 32), false);         InitializeEdit(ref m_editPhone, new Point(344, 64),                         new Size(160, 32), false);         InitializeEdit(ref m_editComment, new Point(80, 104),                         new Size(424, 88), true);   // Multiline!         InitializeLabel(ref m_labelFirst, new Point(-8, 32),                         new Size(88, 23), "First Name:");         InitializeLabel(ref m_labelLast, new Point(256, 32),                         new Size(88, 23), "Last Name:");         InitializeLabel(ref m_labelID, new Point(-8, 72),                         new Size(88, 23), "ID Number:");         InitializeLabel(ref m_labelPhone, new Point(256, 72),                         new Size(88, 23), "Phone Number:");         InitializeLabel(ref m_labelComments, new Point(-8, 112),                         new Size(88, 23), "Comments:");             // Configure the form itself         this.AutoScaleBaseSize = new Size(5, 13);         this.ClientSize = new Size(528, 206);         this.Controls.AddRange(new Control[] {             m_labelComments, m_labelPhone, m_labelID,             m_labelLast, m_labelFirst, m_editFirst,             m_editLast, m_editID, m_editPhone, m_editComment                                              });         this.FormBorderStyle = FormBorderStyle.FixedDialog;         this.MaximizeBox = false;         this.Text = "College Registration";         ResumeLayout(false);     }         // Helper function to create an InkEdit control     private void InitializeEdit(ref InkEdit io_edit, Point in_pt,                                 Size in_size, bool in_fMultiline)     {         io_edit = new InkEdit();         io_edit.Location = in_pt;         io_edit.Size = in_size;         io_edit.Multiline = in_fMultiline;     }         // Helper function to create a label     private void InitializeLabel(ref Label io_label, Point in_pt,                                  Size in_size, string in_strText)     {         io_label = new Label();         io_label.Location = in_pt;         io_label.Size = in_size;         io_label.Text = in_strText;         io_label.TextAlign = ContentAlignment.MiddleRight;     } }

As you can see from running the CollegeBasic application, InkEdit controls have all the keyboard-based text-editing facilities available to standard edit controls. You can use the keyboard to type in the control, move the cursor, and copy and paste text. InkEdit controls, just like the RichTextBox control, can be either multiline or single line. By default, both types of controls are multiline.

You probably noticed right away that, although there are a lot of similarities, the InkEdit control uses a different screen cursor from RichTextBox when you hover the pen or mouse over it. It s a round black dot, reflecting the type of ink that would be created if you wrote with the pen into the control. Using default settings, you can ink into the control only with a pen. The mouse behaves as it normally does in a RichTextBox control so you can use it to select text and position the insertion caret.

Try inking into the InkEdit controls. If you re on a Tablet PC, the ink you ve written is converted (after a slight pause) into text and inserted at the position of the caret. You can change the position of the caret by tapping where you want the caret. With a little practice, you can also select text by pressing and holding at the start of your selection. If you hold the pen down long enough, the cursor turns into a selection cursor, at which point you can drag to select underlying text. These basic text-editing features are all accessible using the pen.

The InkEdit control was designed so that most of its default behavior would be acceptable for everyday use. In the following sections, we ll talk about some settings you should be aware of when customizing an InkEdit control.

Basic Properties

InkMode is a key property of InkEdit. Using InkMode, you can choose whether the InkEdit control accepts ink and possibly gestures as well. By default, InkMode is set to InkAndGesture, allowing the control to accept both ink and gestures. The InkEdit control supports several one-stroke gestures that make it possible to Return, Tab, Space, and Backspace directly within the control. The Tablet PC Platform SDK documentation gives details on how to trigger these gestures. You can also set InkMode to Ink, which will disable gestures but still allow the capture of digital ink. To turn off both ink and gestures entirely, set InkMode to Disabled. Even if InkMode is Disabled, contents of the InkEdit control can still be modified just like a standard RichTextBox. The InkMode property controls only whether digital ink will be captured and whether gestures will be recognized.

You can query InkEdit for its read-only Status property, which tells you whether it s currently Idle, Collecting, or Recognizing ink. The most common reason for checking the Status property is to fulfill requirements imposed by the SDK. Specifically, there are a number of InkEdit properties and methods that can be changed or called only when the control is Idle. It s somewhat of a drag that InkEdit has this requirement because it amounts to requiring a while loop whenever you want to change some properties or call some methods. For instance, you can set InkMode only when the Status property is Idle, requiring code such as the following:

while (edit.Status != InkEditStatus.Idle) { // Wait a little while for user input and/or recognition to // end before trying again System.Threading.Thread.Sleep(100); } edit.InkMode = InkMode.Disabled;

Even this code isn t satisfactory, since the platform doesn t offer any guarantees that it won t change Status from another thread. Thus there is no bulletproof way to fulfill the platform s requirement that Status be Idle when modifying InkMode. Worse yet, if Status isn t Idle when you change InkMode, an exception is thrown. An equally unsavory alternative is possible:

bool fStatusIsSet = false; while (!fStatusIsSet) { try { edit.InkMode = InkMode.Disabled; fStatusIsSet = true; } catch (Exception) { // Wait a little while for user input and/or recognition to // end before trying again System.Threading.Thread.Sleep(100); } }

The preceding code keeps attempting to set InkMode until it is successful. If the code sets InkMode while the user is entering ink or the control is recognizing ink, the control will throw an exception. To recover, we catch the exception and repeat the process. The purpose of Sleep is to give the user or the control some time to stop inking or recognizing ink before we attempt to set InkMode again.

Whichever way you choose, be aware that some properties and methods require that Status be Idle before they are changed or called. The SDK documentation is usually pretty good at calling out this requirement, but unfortunately it offers no suggestion as to how the requirement should be fulfilled.

The InkInsertMode property affects whether ink is inserted into the control as text or ink. The default setting is InsertAsText. However, if you want digital ink to be inserted as ink into InkEdit, set InkInsertMode to InsertAsInk. Upon recognition, the ink will be inserted as ink unless you later change it to text manually. We ll see how to convert ink manually into text later in the chapter. There are two caveats concerning InkInsertMode. The first is that InkInsertMode can be modified only when InkEdit is running on Windows XP Tablet PC Edition. On all other operating systems, changes to the value will have no effect. The second caveat is that InkEdit will change the size of inserted ink when InkInsertMode is InsertAsInk. InkEdit tries to make inserted ink match the characteristics of nearby text. To do so, it changes the ink s height to match the font height of text at the insertion point. If you find the inserted ink is too small, increase the font size of the InkEdit control.

You can change the appearance of ink that s drawn in InkEdit by changing the control s DrawingAttributes property. When setting this property, you should consider making the ink thick enough to be easily visible but not so thick as to take over the control s area. You can set Color, PenTip, Transparency, and any other properties on DrawingAttributes as well. The default results in a black round-tipped pen. By setting DrawingAttributes, the on-screen pen cursor is updated automatically to reflect the type of ink that is produced.

Mouse-Related Properties

There are two mouse-related properties in InkEdit. The first is the UseMouseForInput property, and with it you can control whether InkEdit interprets mouse movements and mouse actions as if they originated from a pen. In practice, this is equivalent to controlling whether the mouse can create ink in the control. By default, UseMouseForInput is false, ensuring that the mouse behaves as it does in regular RichTextBox controls (that is, it can move the caret and select text). If you set UseMouseForInput to true, the mouse will create digital ink when you click and drag it over InkEdit. When UseMouseForInput is true, you can still use the mouse to set the caret position and to select text you ll just have to imitate the pen in doing those actions by clicking for the former and holding-and-dragging for the latter.

NOTE
Setting UseMouseForInput will not affect the pen s ability to generate ink. A pen can always write into an InkEditcontrol (assuming the current InkMode setting allows it).

InkEdit also has a Cursor property. Unlike most of the Tablet PC Platform s managed API, this cursor refers to the on-screen representation of the mouse location. By setting this property, you change the cursor that is used when the mouse hovers over InkEdit. You can set this property to any object of type System.Windows.Forms.Cursor, although you most often will set it to one of the static cursors available through the System.Windows.Forms.Cursors collection (such as IBeam). You cannot change the pen s on-screen cursor through the Cursor property or any other property. Thus, the pen s cursor will always reflect the type of ink that will be drawn. By default, the Cursor property is null, making the mouse cursor the same as the pen s cursor when hovering over InkEdit. (An exception is that if InkMode is Disabled, the null Cursor results in an I-beam cursor.)

At this point, if you ve been keeping up with the default mouse-related properties, you re probably asking yourself a couple of good questions: Why would UseMouseForInput default to false and Cursor default to null at the same time? Wouldn t that lead to the mouse being unable to ink while having a cursor that indicates inking ability? Indeed, the default mouse-related settings are in conflict with one another. On one hand, the mouse defaults to not generating ink. This makes sense for most applications, in which a user would not want the mouse to ink within the control. On the other hand, by leaving Cursor null, the mouse cursor appears to be a pen tip when hovering over InkEdit, leading the user to believe that the mouse will behave like a pen. For almost all imaginable purposes, you should not leave these two defaults as they are. Instead, you should set one or the other so that they are not in conflict. You will probably set Cursor to IBeam and leave UseMouseForInput as false in most scenarios.

Basic Properties Example

Now that we have a better understanding of the basic properties that are settable on InkEdit, we can look at a more complete example of the CollegeBasic application. The following CollegeFull application shows how you might use the basic properties and mouse-related properties to enhance CollegeBasic.

CollegeFull.cs

//////////////////////////////////////////////////////////////////// // CollegeFull.cs // (c) 2002 Microsoft Press, by Philip Su // This program demonstrates usage of InkEdit controls //////////////////////////////////////////////////////////////////// using System; using System.Drawing; using System.Windows.Forms; using Microsoft.Ink; public class CollegeFull : Form { private InkEdit m_editFirst; // First name private InkEdit m_editLast; // Last name private InkEdit m_editID; // Student ID private InkEdit m_editPhone; // Phone number private InkEdit m_editComment; // Any comments private Label m_labelFirst; private Label m_labelLast; private Label m_labelID; private Label m_labelPhone; private Label m_labelComments; [STAThread] static void Main() { Application.Run(new CollegeFull()); } public CollegeFull() { InitializeComponent(); } private void InitializeComponent() { SuspendLayout(); // Create the InkEdit controls and labels InitializeEdit(ref m_editFirst, new Point(80, 24), new Size(160, 32), false); InitializeEdit(ref m_editLast, new Point(344, 24), new Size(160, 32), false); InitializeEdit(ref m_editID, new Point(80, 64), new Size(160, 32), false); InitializeEdit(ref m_editPhone, new Point(344, 64), new Size(160, 32), false); InitializeEdit(ref m_editComment, new Point(80, 104), new Size(424, 88), true); // Multiline! InitializeLabel(ref m_labelFirst, new Point(-8, 32), new Size(88, 23), "First Name:"); InitializeLabel(ref m_labelLast, new Point(256, 32), new Size(88, 23), "Last Name:"); InitializeLabel(ref m_labelID, new Point(-8, 72), new Size(88, 23), "ID Number:"); InitializeLabel(ref m_labelPhone, new Point(256, 72), new Size(88, 23), "Phone Number:"); InitializeLabel(ref m_labelComments, new Point(-8, 112), new Size(88, 23), "Comments:"); // Configure the form itself this.AutoScaleBaseSize = new Size(5, 13); this.ClientSize = new Size(528, 206); this.Controls.AddRange(new Control[] { m_labelComments, m_labelPhone, m_labelID, m_labelLast, m_labelFirst, m_editFirst, m_editLast, m_editID, m_editPhone, m_editComment }); this.FormBorderStyle = FormBorderStyle.FixedDialog; this.MaximizeBox = false; this.Text = "College Registration"; // This is new! We now customize some InkEdits. CustomizeEditControls(); ResumeLayout(false); } // Helper function to create an InkEdit control private void InitializeEdit(ref InkEdit io_edit, Point in_pt, Size in_size, bool in_fMultiline) { io_edit = new InkEdit(); io_edit.Location = in_pt; io_edit.Size = in_size; io_edit.Multiline = in_fMultiline; // Choose ONE of the two following lines. Either // let the mouse ink in the control, or show the // IBeam cursor when the mouse hovers over. io_edit.Cursor = System.Windows.Forms.Cursors.IBeam; // io_edit.UseMouseForInput = true; } // Helper function to create a label private void InitializeLabel(ref Label io_label, Point in_pt, Size in_size, string in_strText) { io_label = new Label(); io_label.Location = in_pt; io_label.Size = in_size; io_label.Text = in_strText; io_label.TextAlign = ContentAlignment.MiddleRight; } // Customizes some of the edit controls private void CustomizeEditControls() { DrawingAttributes da = new DrawingAttributes(); da.Color = Color.Red; da.PenTip = PenTip.Rectangle; da.Width = 10; // Change the First Name field to red ink m_editFirst.DrawingAttributes = da; // Disable inking in the Last Name field while (m_editLast.Status != InkEditStatus.Idle) { System.Threading.Thread.Sleep(100); } m_editLast.InkMode = InkMode.Disabled; // Keep ink as ink in the Comment field m_editComment.InkInsertMode = InkInsertMode.InsertAsInk; } }

CollegeFull contains a few small changes from CollegeBasic. In InitializeEdit, we set Cursor to an I-beam cursor and leave UseMouseForInput false:

 // Choose ONE of the two following lines. Either // let the mouse ink in the control, or show the // IBeam cursor when the mouse hovers over. io_edit.Cursor = System.Windows.Forms.Cursors.IBeam; // io_edit.UseMouseForInput = true;

We also introduce a new function, CustomizeEditControls. It sets the First Name field to use a red rectangle-tipped pen:

 DrawingAttributes da = new DrawingAttributes(); da.Color = Color.Red; da.PenTip = PenTip.Rectangle; da.Width = 10; // Change the First Name field to red ink m_editFirst.DrawingAttributes = da;

It then disables the Last Name field from accepting ink or gestures:

 // Disable inking in the Last Name field while (m_editLast.Status != InkEditStatus.Idle) { System.Threading.Thread.Sleep(100); } m_editLast.InkMode = InkMode.Disabled;

Finally it prevents automatic conversion of ink into text in the Comment field:

 // Keep ink as ink in the Comment field m_editComment.InkInsertMode = InkInsertMode.InsertAsInk;

The CollegeFull application serves as an example of basic InkEdit use. We ll turn our attention to more advanced ways to use InkEdit in the following sections.

Working with Ink

Up to this point, we have seen how to change the behavior of InkEdit in some simple ways. But we have not yet been able to work with any ink generated by the user. Now we will consider two properties that deal with the ink within an InkEdit control.

Accessing Ink with SelInks

The SelInks property gets or sets the selected ink in an InkEdit control. When you read from the SelInks property, it returns clones of all the ink objects in the selection. Changes to these clones will not affect the ink that is in the selection. If you want to change the selected ink, you need to explicitly set the SelInks property (you can set the SelInks property only on Windows XP Tablet PC Edition):

 Ink[] rgink = m_edit.SelInks; foreach (Ink ink in rgink) { ink.DeleteStroke(ink.Strokes[0]); } m_edit.SelInks = rgink; // Set the ink back into the control

One oddity of the InkEdit control is that it never accepts any changes to DrawingAttributes. For instance, the following code will not change the color of selected ink:

 Ink[] rgink = m_edit.SelInks; foreach (Ink ink in rgink) { foreach (Stroke stroke in ink.Strokes) { stroke.DrawingAttributes.Color = Color.Red; } } m_edit.SelInks = rgink; // Set the ink back into the control

InkEdit refuses to allow you to change the appearance of the ink using DrawingAttributes on the selected Ink objects because it wants the ink to look like the text around it. InkEdit will scale ink according to the font size of nearby text and will also change ink color to match the color of nearby text. The only way to affect the visual appearance of ink in an InkEdit control is to change the ink s text properties. As an example, the following code changes any selected ink to red:

 m_edit.SelectionColor = Color.Red;

This might cause you to wonder why the SelInks property is settable at all, given that you cannot use it to change the appearance of selected ink. You can, however, use it to change other ink properties. For instance, you can use the SelInks property to insert custom data into the selected ink objects ExtendedProperties collections.

Toggling Ink Display with SelInksDisplayMode

By default, InkEdit inserts all ink as text upon recognition. However, if you set InkInsertMode to InsertAsInk, InkEdit inserts Ink objects instead of text. One of the resulting benefits of this is that the inserted Ink objects can toggle between displaying themselves as ink and displaying themselves as text. Ink objects in an InkEdit control maintain all their properties even when they are displayed as text.

You can toggle the SelInksDisplayMode property between InkDisplayMode.Ink and InkDisplayMode.Text. When you change the SelInksDisplayMode property, you change how InkEdit displays the selected ink. The following code will toggle how selected Ink objects display themselves:

if (m_edit.SelInksDisplayMode == InkDisplayMode.Ink) { m_edit.SelInksDisplayMode = InkDisplayMode.Text; } else { m_edit.SelInksDisplayMode = InkDisplayMode.Ink; }

If the selection contains mixed ink and text, SelInksDisplayMode will return InkDisplayMode.Ink. Keep in mind when setting SelInksDisplayMode to InkDisplayMode.Text that SelInks will not return any Ink objects that are displayed as text. This anomaly in SelInks behavior means there is no way to detect the presence of Ink objects in a selection if those Ink objects are set to display themselves as text. The following code illustrates this problem:

m_edit.SelInksDisplayMode = InkDisplayMode.Text; if (m_edit.SelInks.Length > 0) { // This if statement will NEVER be true in InkEdit }

Recognizing Ink and Gestures

An InkEdit control will automatically capture and recognize ink as well as gestures. But what if you want to fine-tune its recognition in some way? Fortunately, you can change both the timing and the nature of ink recognition. You can also modify InkEdit s built-in set of gestures. This flexibility makes InkEdit fairly customizable to your application s needs.

Changing Recognition

One major way to affect the results obtained from InkEdit s ink recognition is to change the Recognizer it uses. This capability is particularly useful if your application accepts input in multiple languages. If left unmodified, InkEdit uses the default recognizer obtainable through Recognizers.GetDefaultRecognizer. Most often, you will be perfectly happy with the recognizer that s used, but you will want to give it some hints to improve its accuracy. We introduced Factoids in Chapter 7 as a means to offer hints to a recognizer. By supplying a Factoid based on contextual clues known only to you (the application developer), you can improve recognition accuracy significantly. Factoids are particularly important in InkEdit fields, where there is often little or no outside context on which the recognizer can rely. You should set the Factoid property in InkEdit whenever there is an appropriate Factoid that will help narrow the recognizer s context.

The following CollegeFact application shows how factoids improve recognition accuracy. The SetFactoids method applies the Number and Telephone factoids to the Student ID and Phone Number fields, respectively. In addition, it sets the First Name and Last Name Factoid property to None so that unique names are not coerced into dictionary words. Other than these modifications, CollegeFact is largely based on CollegeBasic.

CollegeFact.cs

//////////////////////////////////////////////////////////////////// // CollegeFact.cs // (c) 2002 Microsoft Press, by Philip Su // This program demonstrates Factoid usage in InkEdit controls //////////////////////////////////////////////////////////////////// using System; using System.Drawing; using System.Windows.Forms; using Microsoft.Ink; public class CollegeFact : Form { private InkEdit m_editFirst; // First name private InkEdit m_editLast; // Last name private InkEdit m_editID; // Student ID private InkEdit m_editPhone; // Phone number private InkEdit m_editComment; // Any comments private Label m_labelFirst; private Label m_labelLast; private Label m_labelID; private Label m_labelPhone; private Label m_labelComments; [STAThread] static void Main() { Application.Run(new CollegeFact()); } public CollegeFact() { InitializeComponent(); } private void InitializeComponent() { SuspendLayout(); // Create the InkEdit controls and labels InitializeEdit(ref m_editFirst, new Point(80, 24), new Size(160, 32), false); InitializeEdit(ref m_editLast, new Point(344, 24), new Size(160, 32), false); InitializeEdit(ref m_editID, new Point(80, 64), new Size(160, 32), false); InitializeEdit(ref m_editPhone, new Point(344, 64), new Size(160, 32), false); InitializeEdit(ref m_editComment, new Point(80, 104), new Size(424, 88), true); // Multiline! InitializeLabel(ref m_labelFirst, new Point(-8, 32), new Size(88, 23), "First Name:"); InitializeLabel(ref m_labelLast, new Point(256, 32), new Size(88, 23), "Last Name:"); InitializeLabel(ref m_labelID, new Point(-8, 72), new Size(88, 23), "ID Number:"); InitializeLabel(ref m_labelPhone, new Point(256, 72), new Size(88, 23), "Phone Number:"); InitializeLabel(ref m_labelComments, new Point(-8, 112), new Size(88, 23), "Comments:"); // Configure the form itself this.AutoScaleBaseSize = new Size(5, 13); this.ClientSize = new Size(528, 206); this.Controls.AddRange(new Control[] { m_labelComments, m_labelPhone, m_labelID, m_labelLast, m_labelFirst, m_editFirst, m_editLast, m_editID, m_editPhone, m_editComment }); this.FormBorderStyle = FormBorderStyle.FixedDialog; this.MaximizeBox = false; this.Text = "College Registration"; // Set the Factoids that will be used SetFactoids(); ResumeLayout(false); } // Helper function to create an InkEdit control private void InitializeEdit(ref InkEdit io_edit, Point in_pt, Size in_size, bool in_fMultiline) { io_edit = new InkEdit(); io_edit.Location = in_pt; io_edit.Size = in_size; io_edit.Multiline = in_fMultiline; } // Helper function to create a label private void InitializeLabel(ref Label io_label, Point in_pt, Size in_size, string in_strText) { io_label = new Label(); io_label.Location = in_pt; io_label.Size = in_size; io_label.Text = in_strText; io_label.TextAlign = ContentAlignment.MiddleRight; } // Set our factoids according to what's expected. private void SetFactoids() { m_editFirst.Factoid = Factoid.None; m_editLast.Factoid = Factoid.None; m_editID.Factoid = Factoid.Number; m_editPhone.Factoid = Factoid.Telephone; } }

Recognition Timing

When inking in a typical InkEdit control, you ll notice that it recognizes ink and turns it into text when one of two things happens either the InkEdit control loses focus or some time passes since you ve last written into the control.

You can control the amount of time that passes before InkEdit converts the ink into text by changing the RecoTimeout property. You can set the number of milliseconds you want InkEdit to wait before beginning recognition through this property:

m_edit.RecoTimeout = 5000; // Wait for 5 seconds before reco!

Setting RecoTimeout to too short an interval risks interrupting the user in the middle of a multistroke word, whereas making the interval too long risks boring the user. You can also set RecoTimeout to zero, which does not mean that ink gets recognized immediately when you lift the pen. Zero actually means infinite in this case. If RecoTimeout is zero, automatic recognition simply will not happen.

But even if RecoTimeout is zero, you can manually force recognition to occur at any time by calling Recognize. The Recognize call is synchronous, so it will not return until the recognition and conversion to text has been completed. One point to remember is that Recognize will convert only freshly added ink into text. It will not convert any previously inserted ink added when InkInsertMode was InsertAsInk, for instance.

The following sample program shows how RecoTimeout and Recognize can work together to make InkEdit recognize ink on demand. It sets RecoTime out to zero and calls Recognize only when the Recognize Now button is clicked.

DelayedReco.cs

//////////////////////////////////////////////////////////////////// // DelayedReco.cs // (c) 2002 Microsoft Press, by Philip Su // This program demonstrates delayed recognition with InkEdit //////////////////////////////////////////////////////////////////// using System; using System.Windows.Forms; using System.Drawing; using Microsoft.Ink; public class DelayedReco : Form { private InkEdit m_edit; private Label m_label; private Button m_btnReco; [STAThread] static void Main() { Application.Run(new DelayedReco()); } public DelayedReco() { InitializeComponent(); m_edit.RecoTimeout = 0; // Prevent automatic recognition } // Handle the click of the Recognize Now button private void RecoBtnClick(object sender, EventArgs e) { m_edit.Recognize(); } private void InitializeComponent() { SuspendLayout(); // Create the InkEdit control and label m_edit = new InkEdit(); m_edit.Location = new Point(65, 24); m_edit.Size = new Size(424, 88); m_edit.Cursor = System.Windows.Forms.Cursors.IBeam; m_label = new Label(); m_label.Location = new Point(0, 24); m_label.Size = new Size(60, 23); m_label.Text = "Ink Here:"; m_label.TextAlign = ContentAlignment.MiddleRight; m_btnReco = new Button(); m_btnReco.Location = new Point(65, 120); m_btnReco.Size = new Size(100, 23); m_btnReco.Text = "Recognize Now"; m_btnReco.Click += new EventHandler(RecoBtnClick); // Configure the form itself this.AutoScaleBaseSize = new Size(5, 13); this.ClientSize = new Size(528, 150); this.Controls.AddRange(new Control[] { m_label, m_edit, m_btnReco }); this.FormBorderStyle = FormBorderStyle.FixedDialog; this.MaximizeBox = false; this.Text = "InkEdit Delayed Recognition"; ResumeLayout(false); } }

Whether you call Recognize programmatically or InkEdit calls it automatically, the control always fires the Recognition event. You can attach a delegate to receive this event, which fires immediately after recognition but before InkEdit inserts the recognized text. Unfortunately, you cannot change the text that will be inserted into the control by calling ModifyTopAlternate on the RecognitionResult passed in the event s InkEditRecognitionEventArgs parameter. On Windows XP Tablet PC Edition, calling ModifyTopAlternate on an InkEdit s RecognitionResult will have no effect.

Gestures in InkEdit

You have limited control over gestures in an InkEdit control. You can toggle the detection of gestures and you can respond to the Gesture event. The following gestures are on by default:

Gesture

Action

DownLeft, DownLeftLong

Return

UpRight, UpRightLong

Tab

Right

Space

Left

Backspace

Let s dive right into the Gesticulator sample program. Gesticulator has an InkEdit control that responds to two additional gestures: Scratch-Out and Star.

Gesticulator.cs

//////////////////////////////////////////////////////////////////// // Gesticulator.cs // (c) 2002 Microsoft Press, by Philip Su // This program demonstrates gestures in InkEdit //////////////////////////////////////////////////////////////////// using System; using System.Drawing; using System.Windows.Forms; using Microsoft.Ink; public class Gesticulator : Form { private InkEdit m_edit; private Label m_label; [STAThread] static void Main() { Application.Run(new Gesticulator()); } public Gesticulator() { InitializeComponent(); RegisterForGestures(); // Here's where the action's at! } private void InitializeComponent() { SuspendLayout(); // Create the InkEdit control and label m_edit = new InkEdit(); m_edit.Location = new Point(65, 24); m_edit.Size = new Size(424, 88); m_edit.Cursor = System.Windows.Forms.Cursors.IBeam; m_label = new Label(); m_label.Location = new Point(0, 24); m_label.Size = new Size(60, 23); m_label.Text = "Ink Here:"; m_label.TextAlign = ContentAlignment.MiddleRight; // Configure the form itself this.AutoScaleBaseSize = new Size(5, 13); this.ClientSize = new Size(528, 125); this.Controls.AddRange(new Control[] { m_label, m_edit }); this.FormBorderStyle = FormBorderStyle.FixedDialog; this.MaximizeBox = false; this.Text = "Gesticulator"; ResumeLayout(false); } // Let the InkEdit control know which additional // gestures to inform us of private void RegisterForGestures() { // SetGestureStatus requires us to be idle while (m_edit.Status != InkEditStatus.Idle) { System.Threading.Thread.Sleep(100); } m_edit.SetGestureStatus(ApplicationGesture.Scratchout, true); m_edit.SetGestureStatus(ApplicationGesture.Star, true); m_edit.Gesture += new InkEditGestureEventHandler( GestureHandler); } private void GestureHandler(object in_sender, InkEditGestureEventArgs in_args) { switch (in_args.Gestures[0].Id) { case ApplicationGesture.Scratchout: m_edit.Clear(); // Remove all text break; case ApplicationGesture.Star: m_edit.SelectedText = "*"; // Insert an asterisk break; } } }

The Gesticulator constructor first initializes its components and then calls RegisterForGestures. The actual registration for gestures is a simple two-step process. First you express your interest in detecting a particular gesture by calling SetGestureStatus (the second parameter determines whether you d like to hear about the gesture):

 m_edit.SetGestureStatus(ApplicationGesture.Scratchout, true); m_edit.SetGestureStatus(ApplicationGesture.Star, true);

In this case, we ve expressed interest in both the Scratchout and Star gestures. You can call SetGestureStatus only while InkEdit is idle, so the program goes through the familiar idle-detection loop prior to the code shown here. Once you ve expressed interest, the control will start to detect these two additional gestures. The second step in the process is to register for the gesture event:

 m_edit.Gesture += new InkEditGestureEventHandler( GestureHandler);

The GestureHandler function does the real work of responding to any triggered gestures. In it, we look for and process the two gestures of interest:

 switch (in_args.Gestures[0].Id) { case ApplicationGesture.Scratchout: m_edit.Clear(); // Remove all text break; case ApplicationGesture.Star: m_edit.SelectedText = "*"; // Insert an asterisk break; }

When we receive the Scratchout gesture, we remove all text from the InkEdit control (it inherits the Clear function from its ancestor class, TextBoxBase). The Star gesture ends up either inserting an asterisk at the caret or replacing the selection with an asterisk, depending on whether you ve got text selected. There s really nothing else to Gesticulator!

You ve probably realized that even though GestureHandler deals only with two gestures, the default InkEdit gestures still work when executed. This is because InkEdit has a gesture handler registered with the Gesture event, independent of anything we ve done. This might make you wonder whether you can turn a few of the default InkEdit gestures off. The good news is that you can just call SetGestureStatus with false as the second parameter for any default InkEdit gestures that you want to turn off. Those gestures will no longer trigger any unwanted behaviors.

If you re interested in finding out whether a gesture is currently turned on, call GetGestureStatus. This method returns whether a given application gesture is being watched for (true) or not watched for (false) by the gesture recognizer. Gestures can also have more than one handler, each performing a different task when InkEdit detects the gesture.

NOTE
Remember that a gesture that s enabled is only being listened for; it doesn t necessarily have to have a handler that does something with it.

InkEdit Parting Thoughts

So far in this chapter we ve shown you how easy it is to integrate InkEdit into application user interfaces. You ve also seen how InkEdit can be reasonably accommodating to customization of its appearance, behavior, and recognition. These characteristics make InkEdit an ideal candidate to help enhance your application s ink awareness, particularly in its ability to accept direct ink input into its dialog box edit controls.

Despite its flexibility, InkEdit has certain limitations. Its biggest one is that it does not expose an InkCollector or InkOverlay interface. Thus, you cannot control specifics about how InkEdit collects ink. For instance, what if you wanted to prevent the user from inking over existing text in the control? Or if you wanted to stop ink from being captured when it flows outside the boundaries of the control? These are things you simply cannot do with InkEdit. Another limitation is that InkEdit does not expose the RecognizerContext it uses to perform recognition. Without access to the RecognizerContext you cannot fine-tune ink recognition. For example, you might want to enforce that recognition adhere to a given Factoid by using the RecognitionFlags value Coerce, which we learned about in Chapter 7. However, because there is no reco context available, there is no RecognitionMode property to set.

We end our exploration into InkEdit with some thoughts about usability. Strictly speaking, on a tablet there is no need for the InkEdit control at all. After all, the Tablet PC Input Panel is able to inject text into regular edit boxes. However, the InkEdit control offers a more direct way for the tablet user to interact with your application without needing to bring up the Input Panel. Successful integration of InkEdit controls goes beyond simply adding these controls to our applications. Usability demands that we answer two questions:

  • How does the user know that an edit control is inkable?

  • Is the writing area of the InkEdit control big enough to write in comfortably?

The Tablet PC Platform, unfortunately, does not answer the first question. InkEdit controls are visually indistinguishable from their RichTextBox counterparts. It s true that while hovering a pen over InkEdit, the user can look for a cursor that s not an I-beam. But this minor distinction is arguably too small to be considered usable as the primary means of determining whether to attempt to write directly in an edit box. Given that it s not at all easy to visually distinguish InkEdit controls from standard TextBox controls, we offer one important piece of advice:

Do not mix InkEdit with plain edit boxes in your application!

Doing so will cause a usability nightmare since the user will need to memorize the edit boxes that are ink aware and those that are not. Instead, if you plan to add even a single InkEdit to your application, consider making all your application s edit controls InkEdit.

You control the answer to the second question whether the InkEdit controls are big enough to write in. Plain edit boxes are typically tall enough to display 10-point fonts, which is not that tall at all (especially compared to handwriting). You can provide a better user experience if you make your InkEdit controls big enough to write in. Sizing the InkEdit controls is a tough balancing act, though. If you make them too big, non-tablet users will wonder what all that space is for. There will also no doubt be the familiar challenges from designers regarding the visual appearance of large edit boxes juxtaposed with small text labels.

All things considered, the InkEdit control is a useful tool in your Tablet PC development arsenal. In cases where your application needs an ink-aware control for data entry, InkEdit is the clear choice.



Building Tablet PC Applications
Building Tablet PC Applications (Pro-Developer)
ISBN: 0735617236
EAN: 2147483647
Year: 2001
Pages: 73

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net