Optimizing Applications for Tablet PC
Microsoft® Tablet PC Platform SDK version 1.7
Microsoft Windows® XP Tablet PC Edition 2005
Summary: Lists methods for taking advantage of Tablet PC features within applications. Discusses use of the Context Tagging tool and the SetInputScope API. Includes sample code that creates a managed wrapper for the SetInputScope API. Includes sample code for an extender control that adds SetInputScope properties to all controls on a form that are derived from the TextBoxBase class. Example code is written in C#. (8 printed pages)
Download the sample code
If you haven't had a chance to play with the new release of Microsoft Windows XP Tablet PC Edition 2005 you don't know what you're missing! Between the new operating system and the Microsoft OneNote™ 2003 Service Pack 1 Preview, I've completely stopped writing with a pen and paper forever. For a guy who works best by writing everything down—I go through five to six of those 500 page notebooks a year—making the transition to all electronic writing is a complete dream come true. It's also got me very interested in writing applications that are optimized for the Tablet PC.
This is especially important as the line between a laptop and a Tablet PC is blurring quickly. Your laptop application runs fine on a Tablet PC, because Windows XP Tablet PC Edition 2005 is a superset of Windows XP Professional; however, with a little effort, you'll make life easier for Tablet PC users. In this article, I discuss the steps you can take to ensure your application takes advantage of the features in Windows XP Tablet PC Edition. The bulk of the article gives you a nice control that makes it easy to influence the Tablet PC Input Panel to assist the user in entering data. What's nice about this control is that it is easy to incorporate into your application. With only a day or so of effort you'll have an application that is very user-friendly for those of us using a Tablet PC.
For this article, I'm assuming that you've read the introductory material in the Tablet PC SDK 1.7 help file. There's a lot of great information in there and it should be the first place you start when contemplating doing Tablet PC development.
When your application first runs on a Tablet PC, you'll probably find out that you've been living the landscape life a little too long. Microsoft has found that most Tablet PC users immediately flip their computer into portrait mode and leave it there. Because I, too, am one of those people, I've continually run into applications that assume landscape orientation. Figure 1 shows a real example of an application that does not take into account portrait mode. Although I won't explicitly embarrass the company that owns the dialog in Figure 1, I hope they soon stop forcing me to switch to landscape to use their application.
Figure 1. Not accounting for portrait mode.
The easy answer for dialog and window sizes is to keep everything inside 768 x 768. That's because the minimum resolution for a Tablet PC is 768 x 1024 for portrait or 1024 x 768 for landscape. However, you can do better than that. In the source code that goes along with this article, I've included a Utilities class with LandscapeScreen and PortraitScreen properties. Use the properties to check for orientation and then have your application react accordingly.
If you've been doing .NET Windows Forms development for a while, you may be wondering why I didn't simply use the Screen.PrimaryScreen property to determine the orientation. The problem is that the value in PrimaryScreen appears to be cached from the startup and does not update when the user changes screen orientation on the Tablet PC. Consequently, I just need to do the lookup myself by calling the Win32 EnumDisplaySettings function to do the magic.
If you want to do advanced form layout or other Tablet PC-specific work when the screen resolution changes, the SystemEvents.DisplaySettingsChanged event triggers whenever the screen resolution or mode changes. In working on an application designed specifically for Tablet PC, I found that it was too much work to have every form in the application request the event. I thought it would be easier if a form handled the event and offered the DisplaySettingChanged as an event off the form itself. The TabletPCAwareForm in the source code is what hooks everything up.
Since I'm on the subject of small, but useful methods, I'll point out that the Utilities class also includes a method to determine if your application is currently running on a Tablet PC, appropriately called the IsTabletPC method. Additional methods include ones to tell you if the user has selected right handed or left handed usage and menu display. Everything in the Utilities class is so trivial it's not worth showing, but I've been glad to have it all in one place.
What's New in the Tablet PC SDK Version 1.7 of the SDK documentation mentions the Context Tagging Tool, which ships with version 1.7 of the SDK, but that mention doesn't even begin to discuss that tool's usefulness. In all the edit fields in your application, you've probably got some sort of limiting or expected text that's appropriate. While you get the wonderful new Input Panel by default whenever the user moves the pen into an input area, the user can enter any values into the handwriting area. This is fine for the multiline edit control where you have the user enter a discussion of the sales contact, but it's not good if they are supposed to enter something like an SMTP-formatted e-mail address. What's even worse is if you have limited the entry by the keyboard through a Regex class to a specific set of input, but the user can write anything they want in Input Panel. Ideally, you'd like the input through both the keyboard and Input Panel to limit in the same way.
The Context Tagging tool makes it extremely easy for you to provide limiting input to Input Panel without even changing your application's code. Using the Context Tagging tool, you point it at the various edit fields in your application and specify the type of input for the field. You can restrict the input to predefined restrictions—such as an SMTP e-mail address or specific numerical values. If you have specific values, you can also specify a phrase list of any length you'd like. For unique items, you can also specify a regular expression. Unfortunately, the regular expression language used by Input Panel does not match the Regex class from the .NET Framework. Later, I discuss these differences when setting a regular expression programmatically for Input Panel. When using the Context Tagging tool to limit input values, this is also called setting the input scope.
The output of the Context Tagging tool is a .ctm file that's created in the same directory as your application. Input Panel reads that file when your application runs. When the user brings up Input Panel in a particular edit control, Input Panel applies the appropriate input limitation to the writing area. Overall, it's quite a beautiful system, one that makes it easy to make your application more effective when on a Tablet PC.
For more information about the Context Tagging tool, I'll refer you to the Tablet PC SDK documentation. I do want to point out two key items about the Context Tagging tool. The first is that the documentation does not emphasize enough that for Windows Forms application you must fill out the AccessibleName property on controls on the form in order for the Context Tagging tool and Input Panel to figure out exactly which control the limiting input should be applied to. This may necessitate going through and setting those values in your forms; however, you should be setting them anyway to make it easier for screen readers and other accessibility software to use your application.
The second issue is that the .ctm file you create with the Context Tagging tool is hard-coded. This is fine for fields that don't change, such as that e-mail address entry field, but it can be a huge problem if you created a phrase list. Currently, there's no way to change or dynamically add values to the .ctm file. While the .ctm file is just an XML file, Microsoft has been very explicit that the syntax will change in future releases. This means that to best support Tablet PC, you will want to look at adding a little programmatic support for Input Panel into your application. That's where the SetInputScope API comes into play.
As you can probably tell from the name, the SetInputScope function indicates to Input Panel what input to accept in the handwriting area. In fact, the SetInputScope family is what Windows XP Tablet PC Edition 2005 uses internally when it processes the .ctm file for your application. If you've already read over the documentation for the Context Tagging tool, some of the items I discuss may sound very familiar.
The only minor issue with the SetInputScope API is that it is does not come with a managed API. The API is only offered as a Win32 API exported from MSCTF.DLL. Because programmatically setting Input Panel is so important for Tablet PC applications, I immediately had to rectify the Microsoft oversight. The code I created for the TipInputScope class completely wraps the API. As you can see by inspecting the code, it's completely safe to use on all flavors of operating system. That's because the code becomes a simple no op if not running on Windows XP Tablet PC Edition 2005.
There are four entry points in the TipInputScope class: two-overloaded SetInputScope and two SetInputScopes functions. In all the functions the first parameter is the handle to the window where you want to limit Input Panel input. The first SetInputScope method takes an additional parameter, which is an InputScopeType enumeration defined at the beginning of the file. The values in the enumeration match exactly those defined by the Win32 API. As you scan down the XML Documentation comments for the InputScopeType enumeration, you can see that many of the common items you'll possibly need are already defined.
Where the TipInputScope class gets a little more interesting is with the second SetInputScope method that takes a regular expression string as the second parameter. As I mentioned earlier, the regular expression is different from those from the Regex class. The basic idea is the same, but it's the actual pattern that's slightly different. Some of the differences are in your favor, while others will make you think quite a bit when trying to come up with the right expression. No matter what type of regular expression you set, keep in mind that what you are setting in Input Panel is the exact string you want recognized by Input Panel and Input Panel alone, rather than with the actual edit control. Finally, your regular expression string can only contain characters from code page 1252, (Latin 1 Western Europe).
While it is nice to have all those InputScopeType enumerations, it would be quite a chore to reproduce them on your own if you wanted to combine them to produce a regular expression. For example, if you had an edit control where you wanted to limit Input Panel input to SMTP e-mail addresses as well as e-mail names as you would enter them into Exchange Server 2003, you'd have quite the expression to build up. Fortunately, the regular expressions that you can pass to the SetInputScope APIs allow you to include those InputScopeType enumerations, making your life easier. If you specify the InputScopeType inside parenthesis and precede the enumeration value with an exclamation point, you're all set. Thus, if you set a regular expression of
(! IS URL), it is the same as specifying the IS_URL input scope as the InputScopeType directly with the first SetInputScope overload method.
There's nothing stopping you from extending the regular expression to include multiple InputScopeType objects. To solve the case where you want to limit Input Panel input to just STMP e-mail addresses or Exchange Server 2003 e-mail names, use the following regular expression:
What the InputScopeType gives in the form of very nice support for predefined, common regular expressions, it takes away on other useful regular expression constructs. In the Regex class you express any lower case alphabetic character with the form
[a-z]; however, Input Panel regular expression syntax does not support the character grouping or the bracket characters. To specify one alphabetic character in the range of a to z, you use:
While you might think the two single character-grouping strings are different, they are actually equivalent regular expressions.
When working on Input Panel regular expressions and Regex expressions in general, I've found it best to use a tool like Chris Sells and Michael Weinhardt's excellent RegexDesigner .NET to do all the expression development. Once I get the expression working in RegexDesigner .NET, I convert all character groupings to Input Panel format and verify that the expressions still work as expected.
The first of the SetInputScopes functions takes every possible option permitted. The key reason for this function is that you can pass in phrase lists that you want to use to extend the recognition. For example, if you are working on software for medical researchers to input patient data, you can include a list of common diseases—which are probably not in the Tablet PC system dictionary—to the list. This enables Input Panel to recognize those unique words more accurately. In addition to the phrase lists, you can also pass in an array of InputScopeType enumeration values to assist in the recognition. When doing so, Input Panel assumes that the InputScopeType enumeration values are connected by a logical OR statement. If you pass
Nothing in Microsoft Visual Basic® .NET) as the array in the InputScopeType enumerations, that indicates to Input Panel that you want the particular words in the phrase list as the only words for Input Panel to recognize. This is useful is an input field that expects only abbreviated Canadian provinces, for example. In most cases, you should pass in at least the IS_DEFAULT InputScopeType to ensure that common words are recognized as the user expects.
The second SetInputScopes function is simply an overload where you can specify an array of the InputScopeTypes values.
To demonstrate all of the TipInputScope class, I've included its unit test, TabletPCAwareFormTests, in the Test directory under the source code. While having a wrapper around the native SetInputScope API is useful, I thought that requiring a user to manually enter the code changes for each control to use the TipInputScope was not only tedious but potentially prone to error as well. I knew there had to be some way of wrapping up a text box automatically so a developer simply set a few properties and got all the magic of Input Panel. To solve this, I developed the TabletPCTipScope control.
As I pondered the idea of a control to make setting the properties easy, my first idea was a user control derived from TextBox that had three additional properties on it: the array of InputScopeTypes, the phrase list array, and the regular expression. While that solution would have worked, it wasn't going to meet my requirement to make it easy to retrofit an application. You would have to update your code to use my new text control instead of the standard TextBox. Additionally, there are other editing controls you may want to have Input Panel working on such as RichTextBox and the DataGridTextBox. With these additional requirements in mind, the idea of an extender control was obvious to me.
Even if you're not familiar with the term extender controls, you've probably used them already. Examples of extender controls are the ErrorProvider and ToolTip controls. When you drag those onto your form, they appear in the extra area at the bottom of the form and, as the name implies, add additional properties to controls in the form. This article deals with the aspects of the extender controls that are specific to Tablet PC. For additional information about creating extender controls, see IExtenderProvider Interface and read James Johnson's very nice article, Getting to Know IExtenderProvider.
The control provided in the source code download is called TabletPCTipScope. To add an instance of the control to your toolbar, select the Wintellect.TabletPC.DLL assembly. A Tablet PC icon appears in your toolbox. To add an instance of the control to your form, simply drag the Tablet PC icon from the toolbox to the form. Once there, TabletPCTipScope control automatically adds three properties to all controls derived from TextBoxBase. That means all text entry types are automatically covered. Figure 3 shows the properties added to a TextBox control, textBox3.
Figure 2. The TabletPCTipScope control in action.
The TabletPCTipScope control adds an event handler to classes derived from TextBoxBase, within which TabletPCTipScope internally calls the SetInputScope API whenever an instance of an object derived from TextBoxBase gets focus. That way you can change the Input Panel properties for that control dynamically and rest assured that you're getting Input Panel recognition exactly as you want it. To change a value dynamically, you need to do it through the TabletPCTipScope variable you have on the form. The following code snippet shows an example of this.
// Allocate a phrase list and set the input scope to IS_DEFAULT InputScopeType temp = new InputScopeType[ 1 ] ; temp = IS_DEFAULT ; String  medterms = new String[ 2 ] ; medterms = Dict.Text ; medterms = Dict.Text ; // Set the new input scopes and phrases tabletPCTipScope1.SetInputScopes ( textBox3 , temp ) ; tabletPCTipScope1.SetPhraseList ( textBox3 , medterms ) ;
I hope I was able to give you an idea of some easy ways you can optimize your application for Tablet PC deployment. The SetInputScope API provides an extremely powerful way of influencing Input Panel with minimal code changes, especially when incorporated with the TabletPCTipScope control. While the TabletPCTipScope control does a pretty good job, I already see some improvements I'd like to make to it. The first is to provide better property editors that give you a hint for the InputScopeType values as well as help with the regular expression (especially parenthesis validation!). The second is to incorporate Input Panel regular expressions automatically in the edit box so setting it for Input Panel automatically provides the validation for the keyboard as well. That way you'd automatically get both for one input. The good news is that I plan on tackling both of those features in future articles.
John Robbins is a cofounder of Wintellect, a software consulting, education, and development firm that specializes in programming for the .NET Framework and Windows. He is also the Bugslayer columnist for MSDN Magazine. His latest book is Debugging Applications for Microsoft .NET and Microsoft Windows (Microsoft Press, 2003). You can contact John at http://www.wintellect.com.