Mobility

Add Keyboard Support to Compact Framework Apps by Trapping Windows Messages

Alan Pulliam

This article discusses:

  • How the .NET Framework and the .NET Compact Framework handle Windows messages
  • P/Invoke and calls between managed and unmanaged code
  • The MessageWindow class and native Windows messages
  • Capturing keyboard events
This article uses the following technologies:
.NET Compact Framework, C#, eMbedded Visual C++

Code download available at:KeyboardSupport.exe(147 KB)

Contents

Features Supported by the Compact Framework
Pocket PCs Don't Have Keyboards...Or Do They?
The Mysterious MessageWindow Class
Native Window Handles for My .NET Controls
Intercepting Windows Messages
Managing Keyboard Focus
Implementing a KeyMessage Event Handler
Putting It All Together
Building and Deploying the Native DLL
Conclusion

The Microsoft® .NET Framework provides access to almost all of the features of the Windows® operating system from managed code. For situations in which the .NET Framework doesn't provide the desired functionality, the P/Invoke mechanism can be used to access unmanaged code, including the native Windows API. For UI code, the .NET Framework Control class provides the Handle and WndProc members to expose the underlying native window handle and messages. With access to the underlying windows messages and to the Windows API, any capability of the Windows OS can be accessed from within a .NET Framework-based app, providing an easy workaround for any features not provided by the .NET Framework.

The .NET Compact Framework is a trimmed-down version of the .NET Framework, designed to run on resource-limited devices. As a result, many features available in the Framework had to be excluded from the compact version in order to maintain its small footprint and performance. While the Compact Framework supports P/Invoke, providing access to the Windows CE platform API, the Compact Framework Control class does not expose underlying native window handles or messages as does its bigger brother. Nevertheless, by using the .NET Compact Framework-only MessageWindow class, P/Invoke, and a few lines of native code, it is possible to use Windows subclassing to access native window handles and messages. This technique can be used in place of the Control class's Handle and WndProc members. The example that this article is based on uses this technique to add standard Tab key navigation to a Compact Framework-based application that runs on a Pocket PC.

Features Supported by the Compact Framework

If you've done much development with the .NET Compact Framework, you've probably tried at some point to use a feature from the .NET Framework and found that your code won't compile. When you check the documentation, you find that feature is not supported by the .NET Compact Framework. The Compact Framework was designed for resource and processor-limited Pocket PC devices running Windows CE, which has similar size constraints. The designers of the .NET Compact Framework have done a great job of anticipating developers' needs and only removing features that aren't appropriate for mobile devices. Nevertheless, it's impossible to anticipate every developer's needs. As I said, in the .NET Framework, you always have access to all the capabilities of the underlying Windows API using P/Invoke and to the underlying Windows messages via the Control class's window procedure (WndProc member). The Compact Framework includes P/Invoke; however, it does not allow direct access to the Control's underlying Windows CE messages. That said, it is still possible to access all the capabilities of the Windows CE operating system; it just requires a little more custom code.

By using P/Invoke and a few Windows CE API calls, it is possible to find the corresponding native window handles for Compact Framework controls. With the native handles, the windows can then be subclassed in a few lines of native code, allowing all Windows messages to be intercepted. Finally, using the special Compact Framework MessageWindow class, these messages can be passed to managed code, allowing a Compact Framework-based application to respond directly to Windows CE message events. As with the Control class's WndProc in the .NET Framework, this can be used to add functionality to Compact Framework-based apps—an ability that's normally only available in native Windows CE code. One feature I personally miss is keyboard support. While most Pocket PCs don't have keyboards, it's still helpful to have keyboard support for applications targeting the Pocket PC when using an external keyboard. In this article, I'll show you how to use subclassing to trap WM_KEYDOWN and other keyboard messages from child controls on a form to add standard Tab key navigation to .NET Compact Framework form-based applications.

Pocket PCs Don't Have Keyboards...Or Do They?

First, let me start by explaining that I'm not the type who creates solutions in search of a problem. While this subclassing technique turned out to be an ideal solution for providing access to Windows messages in Compact Framework-based apps, I was actually trying to solve a very specific problem. I was developing an inventory application for a handheld barcode scanner running on a Windows Mobile™ 2002-based Pocket PC. This device was equipped with the standard stylus and touch screen, but also had an alphanumeric keypad. The client wanted to have custom navigation on the application forms, with the Enter key used in addition to the Tab key to advance focus to the next control on each form, and soft keys functioning like Back and Forward browser buttons to move to and from previous and next forms.

At first, this didn't seem to be much of a challenge. For the most part, it sounded like a normal forms application with an additional key to advance focus to the next control. .NET Controls have TabStop and TabIndex properties that specify whether a control can receive focus and in what order. The .NET Framework automatically sets focus to the first enabled control in the tab order, and then advances focus to the next enabled control in tab order when the user presses the Tab key. I thought it was simply a matter of checking the KeyDown event on the form and providing the same behavior for the Enter key. Indeed, when I took a close look at the .NET Framework docs, I found that the KeyPreview property allowed me to capture control messages at the form level and the GetNextControl method (that's shown in Figure 1) allowed me to find the next control in the tab order.

Figure 1 Using KeyPreview

private void InitializeComponent() { ••• // this sends all child controls' KeyDown events to the form this.KeyPreview = true; ••• this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Form1_KeyDown); } private void SelectNextControl(Control current, bool forward) { Control next = GetNextControl(current,forward); if (next != null && next.CanFocus && next.TabStop) next.Focus(); else SelectNextControl(next,forward); } private void Form1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e) { if (e.KeyCode == Keys.Enter) { SelectNextControl(this.ActiveControl,true); } }

I thought I had gotten off easy, until I discovered that the KeyPreview property didn't appear with IntelliSense® in my Visual Studio® SmartDevice project. When I looked again at the docs, the key phrase "Supported by the .NET Compact Framework" was missing. In fact, the infrastructure for keyboard focus on forms, including TabStop, TabIndex, and GetNextControl, is not included in the Compact Framework. For an application running on a Pocket PC that uses the stylus, this isn't an issue. Each control gets focus when the user touches its client area with the stylus. Since most Pocket PCs don't even have a keyboard, the keyboard focus code isn't needed. However, I had a Pocket PC with a keyboard.

While Compact Framework forms don't have the KeyPreview form property, individual Compact Framework controls can still capture key events. By subscribing to the Control.KeyDown event for each focusable control and using a single event handler, a form can conveniently receive key events in a central location. Like KeyPreview, this also allows keyboard handling at the form level. In fact, with a little code to manage tab order and focus, which I'll cover in a later section, this simple technique can be used to add basic keyboard support to a Compact Framework-based application. This technique is demonstrated in the PPCKeyboardSample1 application accompanying this article.

Unfortunately, this basic key handling technique didn't solve my problem. It worked fine for adding Enter key navigation to my application, but it didn't work with the Tab key. When I dug into the .NET Framework documentation, I found that the Tab key is considered a special command key and is handled internally by the .NET Framework, so the KeyDown event is never fired. This is not specific to the Compact Framework. In the .NET Framework, the Tab key is first handled by the ProcessCmdKey key method and then routed to the ProcessTabKey method. Both of these are protected virtual methods which can be overridden in derived controls, but unfortunately neither of these methods is available in the Compact Framework. Even if they were available, the Compact Framework doesn't support direct visual control inheritance, so creating derived controls would pose additional problems.

My next inclination was to use the safety net provided by the .NET Framework and simply access the WM_KEYDOWN message using the WndProc member of the Control class. When I checked the docs, I found that neither the Handle property nor the WndProc procedure was supported in the Compact Framework. I looked into setting up a Windows systems keyboard hook with the SetWindowsHookEx system call to intercept WM_KEYDOWN messages, but hooks aren't supported in the Windows CE operating system. Finally, I decided to use SetWindowLong to subclass control windows and to intercept all window messages, including WM_KEYDOWN. Unfortunately, both SetWindowsHookEx and SetWindowLong work by accepting a pointer to a function, which Windows calls back on message events. Unlike the .NET Framework, P/Invoke in the Compact Framework does not allow callbacks from Windows CE to .NET code. At this point I was stumped, so I dug out my copy of the Microsoft .NET Compact Framework (Core Reference) and my MSDN® CD for some research.

The Mysterious MessageWindow Class

Of course I knew that the designers of the Compact Framework wouldn't remove all those helpful interoperability features. Fortunately, I found the missing link, or at least the missing link between Windows CE and the Compact Framework in Chapter 22 ("Interoperating with Native Code") of the Microsoft .NET Compact Framework (Core Reference).

To allow managed code to call unmanaged code, including the Windows API, the .NET Framework provides the P/Invoke mechanism. For a good description of P/Invoke on the Compact Framework, see the MSDN article, "An Introduction to P/Invoke and Marshaling on the Microsoft .NET Compact Framework" by Jon Box and Dan Fox. The basic P/Invoke is pretty straightforward. You simply declare the unmanaged function you want to call and map the parameters to .NET types in the import signature:

[DllImport("coredll.dll, EntryPoint="GetFocus")] private extern static IntPtr GetFocus(); ••• control.Focus(); IntPtr handle = GetFocus();

This is a bit of a simplification, especially when dealing with complex types and objects, but Windows API calls, for the most part, use primitive types that map directly to a corresponding .NET type. These are known as blittable types. So that takes care of calls from managed code to unmanaged code, but how does unmanaged code call managed code?

Why would unmanaged code need to call managed code? The answer is asynchronous event notifications. Many asynchronous Windows API calls, such as SetWindowLong, take a function pointer as a parameter. Windows calls back to the function when certain events occur. P/Invoke in the .NET Framework allows .NET delegates to be passed as function pointers, so unmanaged code can directly call managed code. The .NET Framework Control class's Handle and WndProc members allow Windows messages to be posted from unmanaged code to Control objects. Unfortunately, P/Invoke in the Compact Framework doesn't provide any support for function pointers for callbacks and the Compact Framework Control class provides no access whatsoever to underlying windows, so Windows CE messages can't be posted directly to Compact Framework Control objects.

This is where the Compact Framework-only MessageWindow class comes into the picture. This class is included in a special library assembly, Microsoft.WindowsCE.Forms.dll, which must be added to the references list of a SmartDevice project. The MessageWindow class is provided specifically to allow access to Compact Framework managed code from unmanaged code. Like the NativeWindow class in the .NET Framework, the MessageWindow class creates a native window specifically for receiving Windows messages. The MessageWindow class provides access to its native window handle through the Hwnd property and receives messages through the overridden WndProc method.

Now you have a Compact Framework class that can receive Windows messages from Windows CE, but how does that help with asynchronous event notifications and intercepting messages, such as WM_KEYDOWN, from your user interface controls? This is where that tiny bit of native code is required. I'll go into the details of creating the native code DLL with Microsoft eMbedded Visual C++® a little later. Basically, you can write some unmanaged, native code to handle callbacks from Windows CE. The native code then posts a Windows message to the handle of a MessageWindow-derived class. Then you have the ability to intercept the message and handle it in the MessageWindow's overridden WndProc method in managed code.

The Microsoft .NET Compact Framework (Core Reference) and the MSDN article "Using the Microsoft .NET Compact Framework MessageWindow Class" provide examples on the use of the MessageWindow class. One example shows how to find a form's native window handle with the FindWindow call, subclass the window with SetWindowLong, intercept the WM_ACTIVATE message in native code, and finally post a user-defined message to a MessageWindow class in managed code (see the sidebar "Getting A Window Handle" for more information). I realized I could use the same technique to intercept the WM_KEYDOWN messages for controls on a form and handle them before the .NET Framework processed them. This would allow me to use the same focus management code as in my first sample, but with notification of keyboard events for Tab or any other key.

The old adage "the devil is in the details" seems to fit software development perfectly. For this issue, the devil first proved to be figuring out how to get the native window handles for my .NET controls and then ensuring that my subclassing didn't interfere or conflict with the .NET Compact Framework.

Native Window Handles for My .NET Controls

Once again, I started with the subclassing example in the Microsoft .NET Compact Framework (Core Reference). This example uses the FindWindow system call to get the handle of a Compact Framework form. The FindWindow call only returns the handles of top-level windows, which works for .NET-based forms in the .NET Framework. Other system calls must be used to find the handles on child controls.

With a little experimentation, I found that GetFocus and WindowFromPoint work well for controls on a form. GetFocus simply returns the control with focus. In order to use this call, each control must be given focus with the Control.Focus method prior to using this call. Obviously, you wouldn't want to do this when a form is visible, but it can be done inconspicuously in the form's Load event handler before the form is displayed for the first time, as shown in the code you saw a few paragraphs ago.

WindowFromPoint, as the name implies, simply returns the handle of the window containing the given point. Since the .NET Control class provides access to its bounds rectangle, WindowFromPoint can also be used to return the handle of the controls. This call must be used with care, however, because it returns the topmost window when controls are stacked, and doesn't recognize nonvisible controls. There are also a couple of issues with calling WindowFromPoint through P/Invoke. The WindowFromPoint system call expects absolute screen coordinates, not taking into account the Windows CE taskbar at the top of the screen. The .NET Control coordinates, on the other hand, are relative to the parent form, which is positioned below the taskbar. Therefore, the relative control coordinates have to be adjusted to absolute coordinates, which are expected by WindowFromPoint:

int x = control.Top + form.Top; int y = control.Left + form.Left; IntPtr handle = WindowFromPointYX(y,x);

You'll also notice that I'm calling WindowFromPointYX, which has the X and Y coordinates reversed, instead of WindowFromPoint. This is because the WindowFromPoint system call actually expects a POINT structure passed by value. P/Invoke only allows structures to be passed by reference, but as long as they contain blittable types, structures can be passed by value by placing the individual members directly on the stack. The POINT structure consists of two DWORD coordinate parameters, so WindowFromPoint can be called with two .NET integer parameters. Structures are laid out sequentially, but since P/Invoke uses the C calling convention, which places parameters on the stack right to left, the parameters must be reversed. To make it clear what's going on, it's a good idea to give the imported method an alias:

[DllImport("coredll.dll", EntryPoint="WindowFromPoint")] private extern static IntPtr WindowFromPointYX(int y, int x);

As you can imagine, when working with these system calls, especially WindowFromPoint, it's easy to get the wrong window handle. During development, I needed to be able to determine if I was getting the correct handles for my Compact Framework controls. This is where some of the tools that come with eMbedded Visual C++ come in handy.

Microsoft eMbedded Visual C++ includes several Windows CE versions of standard SDK tools, which run remotely on the desktop over an ActiveSync connection. I used Windows CE Remote Spy extensively to view window handles, window class attributes, and Windows messages. Windows CE Remote Spy is more basic than the Spy in the Windows Platform SDK. Windows CE Remote Spy doesn't decode the window style flags, so you will have to use the Winuser.h file from eMbedded Visual C++ to manually decode style attribute flags, some Windows messages IDs, and the wParam and lParam values. Despite a little extra work, this tool is essential for checking window handles and is invaluable for observing how the .NET Compact Framework interacts with Windows CE and responds to Windows messages.

This brings me to my last point about working with a control's native handles. To keep from interfering with the .NET Framework, window handles should not be retrieved until after the .NET Framework has finished creating the native windows for all controls on a form. The form's Load event works well for this. Unlike many other form events, the Load event doesn't correspond directly to a Windows message. It is simply fired once by the .NET Framework prior to the first time a form is displayed, after all initialization is complete.

Intercepting Windows Messages

Once you have the native window handle for a control on a form, the next step is to use subclassing to capture the Windows messages; in my case it's the WM_KEYDOWN message. The MSDN library defines subclassing as "a technique that allows an application to intercept and process messages sent or posted to a particular window before the window has a chance to process them." For a good overview of subclassing, you can refer to the MSDN article, "Safe Subclassing in Win32," by Kyle Marsh.

All native windows interact with the Windows operating system using window procedures, but developers don't typically use window procedures directly in their applications. Instead, application frameworks, including the .NET Framework, take care of this. When you subclass the native windows for .NET controls, you're telling Windows to send messages to your window procedure instead of the .NET Framework-provided window procedure. Obviously, you want your controls' native windows to still work with the .NET Framework, so you have be sure to forward all the messages you don't handle to the original .NET window procedure.

Once again, since the Compact Framework does not allow callbacks from Windows to managed code, the actual subclassing is done in native code. The good news is there's not much code involved and you don't have to worry about purchasing another development environment (eMbedded Visual C++ can be downloaded from the MSDN Mobility Web site). At the end of this article, I will tell you how to download eMbedded Visual C++ and create a native DLL project.

Strictly speaking, to add keyboard support to a Compact Framework-based application, only the WM_KEYDOWN message needs to be intercepted, so I could have evaluated the message type in native code and forwarded just the WM_KEYDOWN message or a custom message to my Compact Framework-based application code as demonstrated in the .NET Compact Framework (Core Reference) sample. Instead, I opted for a more generic architecture and decided to forward all Windows messages to managed code. While this incurs some extra overhead, I decided that the combined benefit of keeping all my application logic together in managed code and the flexibility of reusable subclassing code was well worth a slight performance trade-off.

Therefore, I created a Subclasser utility class, shown in Figure 2, which encapsulates the subclassing of a control's native window and exposes the window's messages to managed code through a .NET event. This class effectively replaces the missing functionality of the full .NET Framework Control class.

Figure 2 Subclasser.cs

using System; using System.Runtime.InteropServices; using System.Collections; using Microsoft.WindowsCE.Forms; namespace PPCTools { /// <summary> /// Subclasser subclasses native windows and fires the MessageEvent /// on all messages. /// </summary> public class Subclasser : IDisposable { private class SubclasserMessageWindow : MessageWindow { private Subclasser subclasser; public SubclasserMessageWindow(Subclasser subclasser) { this.subclasser = subclasser; } protected override void WndProc(ref Message msg) { if (subclasser.Message != null) { MessageEventArgs msgEventArgs = new MessageEventArgs(msg); subclasser.Message(subclasser, msgEventArgs); // allow event handler to modify message parameters msg.Msg = msgEventArgs.Msg; msg.WParam = msgEventArgs.WParam; msg.LParam = msgEventArgs.LParam; msg.Result = msgEventArgs.Result; } } } [DllImport("native.dll")] private extern static void Subclass(IntPtr subclassWindow, IntPtr messageWindow); [DllImport("native.dll")] private extern static void Unsubclass(IntPtr subclassWindow); private SubclasserMessageWindow messageWindow = null; private ArrayList windowHandles = new ArrayList(); /// <summary> /// Fired on messages from all subclassed native windows /// </summary> public event MessageEventHandler Message; /// <summary> /// Default constructor /// </summary> public Subclasser() { messageWindow = new SubclasserMessageWindow(this); } /// <summary> /// Constructs Subclasser and subclasses specified native /// window. Useful when Subclasser is used for single window /// </summary> /// <param name="hWindow">Native window handle</param> public Subclasser(IntPtr hWindow) : this() { AddWindow(hWindow); } public IntPtr Hwnd { get { return messageWindow.Hwnd; } } /// <summary> /// Subclasses specified native window /// </summary> /// <param name="hWindow">Native window handle</param> public void AddWindow(IntPtr hWindow) { windowHandles.Add(hWindow); Subclass(hWindow,messageWindow.Hwnd); } /// <summary> /// Unsubclasses specified native window /// </summary> /// <param name="hWindow">Native window handle</param> public void RemoveWindow(IntPtr hWindow) { windowHandles.Remove(hWindow); Unsubclass(hWindow); } /// <summary> /// Unsubclasses all native windows /// </summary> public void Dispose() { while (windowHandles.Count > 0) { RemoveWindow((IntPtr)windowHandles[0]); } } ~Subclasser() { Dispose(); } } }

Since finding the correct native window handle for a control depends on whether you're working with a form or child control, native handles must be obtained before using this class. I have separate focus management code, described in the next section, which takes care of this.

I designed Subclasser to intercept messages for one or more native windows. A single window handle can be passed in a constructor or multiple windows can be added with the AddWindow method. Subclasser contains only one MessageWindow, so it's not possible to tell which message was meant for which window when there are multiple subclassed windows. This isn't a problem for keyboard handling code, where I'm already keeping tracking of the active focus control. However, if you need to keep track of messages per window, you can simply create a separate Subclasser instance for each of your windows.

When Subclasser receives a message for any of its subclassed windows, it fires its Message event using the MessageEventHandler delegate, as you can see in the following code:

public delegate void MessageEventHandler(object source, MessageEventArgs args);

The event handler can then process or change the message. But to keep from interfering with .NET, messages should be ignored by default. Unless the message handler sets the Result field of the MessageEventArgs to a nonzero value, the native code forwards the message to the .NET message queue for normal processing.Getting A Window Handle

The book Microsoft .NET Compact Framework (Core Reference) shows how the FindWindow system can be used to locate the handle for a Compact Framework form as long as it has a uniquely identifiable title. Even when your forms don't have a unique title or, for that matter, any title at all, you can still use FindWindow to find a form handle by temporarily setting the form's Text property to a unique string, just before calling FindWindow, and restoring the original title afterward:

string oldTitle = form.Text; form.Text = "F5486279-64FC-4f9a-B1ED-EDC941DD05D0"; IntPtr hWnd = FindWindow(form.Text,null); form.Text = oldTitle;

Also, GetActiveWindow can be used as an alternative to FindWindow for forms. The active window is the topmost window which contains the current focus control. GetActiveWindow can be used to return the form's native window handle in form's Load event, which is fired by the .NET Framework just before the form is displayed for the first time.

Subclasser provides a convenient .NET event interface for messages received by the MessageWindow class, but the real work to intercept messages is done in the native Subclass function, which is called by the Subclasser.AddWindow method:

[DllImport("CFNative.dll")] private extern static void Subclass(IntPtr subclassWindow, IntPtr messageWindow); ••• public void AddWindow(IntPtr hWindow) { windowHandles.Add(hWindow); Subclass(hWindow,messageWindow.Hwnd); }

Actually, even the real work is only a few lines of C++ code. Figure 3 shows the header and source file for CSubclasser. CSubclasser.cpp provides the implementation of CSubclasser, a C++ class that encapsulates the subclassing of native windows. It also includes the two C-style entry points, Subclass and Unsubclass, which are required for the P/Invoke.

Figure 3 CSubclasser

CSubclasser.h

class CSubclasser { WNDPROC oldWndProc_; // original .NET window procedure HWND messageWindow_; // Compact Framework MesssageWindow class HWND subclassWindow_; // control's native window static LRESULT APIENTRY SubclassWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); public: CSubclasser(HWND subclassWindow,HWND messageWindow); ~CSubclasser(void); };

CSubclasser.cpp

#include "stdafx.h" #include "CSubclasser.h" // Use MFC map collection to keep track of CSubclasser objects CMapPtrToPtr Subclassers; // Keep track of the Compact Framework control's native window handle // (subclassWindow),the MessageWindow's handle, and the original .NET // window procedure address. CSubclasser::CSubclasser(HWND subclassWindow,HWND messageWindow) { subclassWindow_ = subclassWindow; messageWindow_ = messageWindow; oldWndProc_ = (WNDPROC) SetWindowLong(subclassWindow_, GWL_WNDPROC, (LONG)SubclassWndProc); } // Clean up and restore the original .NET window procedure when the object // is deleted CSubclasser::~CSubclasser() { SetWindowLong(subclassWindow_,GWL_WNDPROC,(LONG)oldWndProc_); } // The window procedure is a static member shared by all CSubclasser // instances. It uses a global map to look up the CSubclasser object by // the hwnd of the message. LRESULT APIENTRY CSubclasser::SubclassWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { LRESULT res = 0; CSubclasser* subclasser = NULL; if (Subclassers.Lookup(hwnd,(void*&)subclasser)) { LRESULT res = SendMessage(subclasser->messageWindow_, msg, wparam, lparam); if (!res) res = CallWindowProc(subclasser->oldWndProc_, hwnd, msg, wparam, lparam); } return res; } // Entry points for .NET Framework P/Invoke EXTERN_C _declspec(dllexport) void Subclass(HWND subclassWindow, HWND messageWindow) { CSubclasser* subclasser = new CSubclasser(subclassWindow, messageWindow); Subclassers[subclassWindow] = subclasser; } EXTERN_C _declspec(dllexport) void Unsubclass(HWND subclassWindow) { CSubclasser* subclasser = NULL; if (Subclassers.Lookup(subclassWindow,(void*&)subclasser)) { Subclassers.RemoveKey(subclassWindow); delete subclasser; } }

The Subclass method takes two parameters: the handle of a native window to subclass and the handle of Subclasser's MessageWindow to forward messages. For each native window, a new CSubclasser instance is created to keep track of the native window handle, the Compact Framework MessageWindow handle, and the original .NET window procedure address. The CSubclasser constructor calls SetWindowLong with the address of its shared SubclassWndProc member. After-ward, Windows calls SubclassWndProc with all messages for the window. For each subclassed window, SubclassWndProc simply sends the messages to the Compact Framework MessageWindow first, and then forwards the unhandled messages to the original .NET window procedure. Note that an MFC map collection, CMapPtrToPtr, is used to look up the CSubclasser for a particular native window since the SubclassWndProc member of CSubclasser is static and is shared among all CSubclasser instances for all subclassed windows.

Managing Keyboard Focus

.NET Framework controls have a TabStop property, which dictates whether the control can accept focus, and a TabIndex property, which dictates in what order it receives focus relative to other focusable controls. While it would be nice to add TabStop and TabIndex properties to a set of derived Compact Frameworks, I chose to do things a little differently. First of all, I wanted to work with the stock controls in the toolbox. Even if I had been inclined to create my own version of all the controls, there is another limitation of the Compact Framework that makes this a little more difficult. In the .NET Framework, it is easy to create custom controls to extend the behavior of an existing control through visual inheritance in the designer. The Compact Framework doesn't support visual inheritance—at least not without some extra work.

The process is a bit complicated and is well beyond the scope of this article. See Chapter 18 (Custom Control) of the Microsoft .NET Compact Framework (Core Reference) or the MSDN article, "Creating Custom Controls for the .NET Compact Framework" for the details involved in creating your own custom Compact Framework controls.

For my purposes, I decided to use a special collection to implement the functionality of TabStop and TabIndex. I created a FocusControls collection class similar to the Form's Controls collection. While the Controls collection contains all the controls on a form, my FocusControls collection contains only the focusable controls on the form. All of the controls in this collection implicitly have TabStop set to true, and the order of controls in this collection determines the TabOrder.

In the .NET Framework, the first control in the tab order receives focus the first time the form is activated. The user can then advance focus to the next control in the tab order using the Tab key. Afterward, when a form is deactivated and reactivated, the last control to have focus automatically regains it. In the Compact Framework, when a form is activated, only the form gets focus. For each form, the FocusControls class remembers which control on a form has focus, and assigns focus whenever the form is activated. Like the Form class (which implements the IControlContainer interface) in the .NET Framework, the FocusControls class has an ActiveControl property, which stores the control with focus:

public Control ActiveControl { get { return activeControl; } set { if (this.Contains(value)) value.Focus(); } }

As with the .NET Framework, the first control in the tab order, or in my case, the first control in the collection, receives focus initially and is assigned to the activeControl. By subscribing to the Form.GotFocus event, the FocusControls class forwards focus to the activeControl whenever the form receives focus:

private void form_GotFocus(object sender, System.EventArgs e) { if (activeControl != null) activeControl.Focus(); }

In addition to advancing focus with the Tab or Enter key, focus can be changed with other input devices. On the desktop, this is usually a left mouse click, while on the Pocket PC it is a stylus touch. The last detail of handling focus is making sure that my activeControl member stays in sync with the UI. By also subscribing to the GotFocus event on all the controls in my collection, I can simply update my activeControl member in the GotFocus handler:

private void control_GotFocus(object sender, EventArgs e) { activeControl = sender as Control; }

The check to determine if activeControl is not null in the form's form_GotFocus event handler is necessary because the GotFocus event is fired before the Load event. The Load event is fired one time just before a form is displayed for the first time. This occurs after the form has been constructed and all its native windows have been created, so this is the ideal point at which to subclass controls on the form. The FocusControls collection subscribes to the form's Load event and implements an event handler, form_Load, where it subclasses all the controls in the collection. Unless the user has explicitly set the ActiveControl property, the activeControl member will not have been set the first time the form is displayed. The code in the form_Load event handler, shown in Figure 4, mimics standard focus behavior and sets the activeControl to the first focusable control, if it hasn't already been set, and finally sets focus to the activeControl, assuming one is available.

Figure 4 form_Load Event Handler

private void form_Load(object sender, System.EventArgs e) { if (!initialized) { initialized = true; foreach (Control control in controls) { subclassControl(control); control.GotFocus += new EventHandler(control_GotFocus); } // If there are focusable controls and none has focus, // set focus to first control if (activeControl == null && controls.Count > 0) activeControl = this[0]; if (activeControl != null) activeControl.Focus(); } }

The private subclassControl method makes use of Windows system calls to get the native window handle for each control and then sets up subclassing for the native window using an instance of the Subclasser class (see Figure 5).

Figure 5 subclassControl

private void subclassControl(Control control) { // Get handle of native window by coordinates of top,left position of // control. Control position is relative to form, which is located // under Window CE task bar. WindowFromPoint is expects absolute // screen position, so adjust coordinates by position of form. int x = control.Top + form.Top; int y = control.Left + form.Left; IntPtr handle = WindowFromPointYX(y,x); // Alternate way to get handle of control //control.Focus(); //IntPtr handle = GetFocus(); subclasser.Add(handle); }

Finally, FocusControls implements an event handler, subclasser_MessageEvent, for the Subclasser Message event, which is fired on every Windows message for subclassed native windows, as shown in Figure 6. The subclass_MessageEvent handler ignores all events except for keyboard messages, which it forwards to any listeners on its own KeyMessage event.

Figure 6 Implementing subclasser_MessageEvent

public void subclasser_MessageEvent(object source, MessageEventArgs args) { switch(args.Msg) { case WindowsMessages.WM_KEYDOWN: case WindowsMessages.WM_CHAR: case WindowsMessages.WM_KEYUP: case WindowsMessages.WM_SYSKEYDOWN: case WindowsMessages.WM_SYSCHAR: case WindowsMessages.WM_SYSKEYUP: // capture all keyboard messages if (KeyMessage != null) KeyMessage(this,args); break; } }

While I could have handled the Tab and Enter keys internally in subclasser_MessageEvent and advanced focus internally, I chose to simply expose keyboard events with the KeyMessage event and let the form decide what to do in its KeyMessage event handler. This design allows forms to implement validation logic similar to the Validating and Validated events found in the .NET Framework before advancing focus.

Implementing a KeyMessage Event Handler

Sometimes you may want to check for a special key, such as a Control-C (break), Alt-F (file menu accelerator), or a capital letter. These are generated by simultaneously holding down the Control, Alt, or Shift keys and pressing another key. The Control, Alt (or Menu), and Shift keys are known as modifiers because they modify the state of other regular keys. FocusControls fires its KeyMessage event for each raw key message from Windows, so you could keep track of the modifier key and regular keys' KEY_DOWN messages.

Luckily there's an easier approach. Windows keeps track of the state of Modifier keys for you. You can retrieve the state of these keys with the GetKeyState system call. Figure 7 shows a sample event handler, focusControls_KeyMessage, for the FocusControls KeyMessage event. For each WM_KEYDOWN and WM_SYSKEYDOWN message from a control, the handler checks the Modifier state with GetKeyState, constructs a KeyEventArgs object, and fires the form's KeyDown event.

Figure 7 focusControls_KeyMessage

private void focusControls_KeyMessage(object sender, MessageEventArgs args) { try { // Create KeyEventArgs to fire OnKeyDown event int keyCode = (int)args.WParam; int keyModifier = 0; if (GetKeyState((int)Keys.ShiftKey) < 0) keyModifier |= (int)Keys.Shift; if (GetKeyState((int)Keys.ControlKey) < 0) keyModifier |= (int)Keys.Control; if (GetKeyState((int)Keys.Menu) < 0) keyModifier |= (int)Keys.Alt; if (args.Msg == WindowsMessages.WM_KEYDOWN || args.Msg == WindowsMessages.WM_SYSKEYDOWN) { KeyEventArgs keyEventArgs = new KeyEventArgs((Keys)(keyCode | keyModifier)); OnKeyDown(keyEventArgs); args.Result = keyEventArgs.Handled ? (IntPtr)1 : IntPtr.Zero; } } catch (Exception e) { // marshal exception back to main UI message pump exception = e; timer1.Interval = 1; timer1.Enabled = true; } }

You'll also notice that this KeyMessage handler code has a try-catch block with some unusual timer code. If you have a try-catch exception-handling block around the main thread of your application, you'll quickly discover that, by default, it doesn't catch exceptions that are thrown while handling subclassed messages. While these messages are handled by the same Windows CE thread that is running the main message queue of the application, they are running in a different .NET stack context. This is because the native code window procedure is calling SendMessage, which directly calls the MessageWindow window procedure rather than going through the application's message queue.

You can still catch exceptions thrown while handling subclassed messages by marshaling the exceptions back to the stack context of the application's main message queue. This is where the timer is used. The System.Windows.Forms.Timer class performs the act of firing events by posting messages back to the message queue of the window on which it was created, regardless of where the forms timer was started. To marshal an exception in the subclassed message handler code, you should catch the exception locally, save it to a member variable, and then fire the forms timer. The forms timer will be signaled by a message in the main message queue and the timer event handler can then re-throw the exception in the main UI stack context:

private void timer1_Tick(object sender, System.EventArgs e) { // get timer message and re-throw exception timer1.Enabled = false; throw exception; }

Putting It All Together

Figure 8 shows how both the Subclasser and FocusControls classes work with Windows messages and the .NET Compact Framework. After obtaining native window handles for .NET Controls with Windows systems calls, the windows are subclassed in native code with SetWindowLong, which causes their messages to be passed to CSubclasser's SubclassWndProc. Then using SendMessage, SubclassWndProc sends the messages to the MessageWindow-derived class owned by the managed Subclasser class. Subclasser exposes Windows messages in managed code via its Message event. FocusControls, the class responsible for managing keyboard focus of controls on a form, implements an event handler for its Subclasser instance. The handler ignores all messages for form controls, except keyboard messages, which are exposed through the FocusControl's KeyMessage event.

Figure 8 Window Subclassing and Message Flow

Figure 8** Window Subclassing and Message Flow **

The Subclasser and FocusControls event handlers can indicate that the message event has been handled, which prevents the message from being sent to its corresponding .NET Control class. However, to keep from interfering with the normal operation of the .NET Framework, all Windows messages are typically forwarded by CSubclasser's SubclassWndProc to the .NET Compact Framework Control's internal WndProc method. This allows the .NET Framework to interact with native windows and triggers message-related .NET events, such as KeyDown, Paint, GotFocus, LostFocus, and so on.

Building and Deploying the Native DLL

To write Windows CE native code for the Pocket PC, you need to use eMbedded Visual C++, which can be downloaded from https://msdn.microsoft.com/mobility/downloads/sdks. Be aware, however, that different versions of eMbedded Visual C++ are targeted to specific versions of Windows CE and Pocket PC, so it's tricky to figure out exactly what to download. Windows Mobile 2002 for Pocket PC runs on Windows CE 3.0 and requires eMbedded Visual C++ 3.0, which is included in the Pocket PC SDK and eMbedded Visual Tools 3.0. The newer Windows Mobile 2003 for Pocket PC runs on Windows CE .NET (Windows CE 4.2) and requires eMbedded Visual C++ 4.0, which is packaged in the SDK for Windows Mobile 2003-based Pocket PCs.

Once you have downloaded the appropriate version, the project wizards make creating the native DLL project very easy. P/Invoke can only call into a DLL, so choose WCE Dynamic-link Library or WCE MFC AppWizard (DLL) for the project type. I chose an MFC DLL simply for the collection classes. If you use MFC, be sure to choose the shared MFC DLL project and build a release DLL target, since the release versions of the MFC shared libraries are already included on the Pocket PC. This will result in the smallest footprint for the native DLL.

While you can use C++ to write your native code, you will need to export simple C-style functions for P/Invoke. Each exported function should be prefixed with:

EXTERN_C _declspec(dllexport)

The EXTERN_C specifies the C calling convention, which is required by the Compact Framework P/Invoke and _declspec(dllexport) indicates that the method is to be exported. For complete details on how to format functions for P/Invoke, see the MSDN article "Writing Unmanaged Functions for Microsoft .NET Compact Framework-based Applications".

Unlike Compact Framework SmartDevice projects in Visual Studio, in eMbedded Visual C++ you'll have to specify the platform that you're targeting and build a separate binary for each. If you've been doing much Compact Framework development, you may have found the Pocket PC emulator (which runs on the desktop) useful. You may be surprised to learn that subclassing will work with the emulator as well. In addition to your target Pocket PC platform, you'll need to create an x86 version of the native DLL for use in the emulator.

Since the native code is in a traditional DLL rather than a .NET assembly, Visual Studio .NET debugging and deployment tools are unaware of the dependency on the native DLL. The DLL must either be in the same directory as the Compact Framework-based application or on the device path. If P/Invoke cannot locate a DLL, you will get a "MissingMethodException" whenever you try to make a native call.

When you build the native DLL in eMbedded Visual C++, by default it automatically deploys the DLLs to the Windows directory of a Pocket PC, which is always in the device system path. You can also use the file explorer in ActiveSync® to place DLLs manually.

Copying the DLL to the emulator isn't quite as straightforward. You have to set up a network share between the emulator and your desktop PC to transfer the native DLL to the emulator image. This process is described in the MSDN article "Using the Emulator in Smart Device Projects".

This takes care of the development environment, but you'll want to include the native DLL in your application installation as well. Visual Studio .NET includes the CabWiz application installation tool, "Build Cab File" on the Build menu, that generates self-installing CAB archives for the Pocket PC. When you run CabWiz, it creates a CAB directory under the project containing self-installing CAB files for all supported Pocket PC platforms. Additionally, it creates a BuildCab batch file and a CabWiz INF file in the project's obj directory. To be included in the CAB, the native DLL needs to be manually added to the INF file. The MSDN article, "Generating Custom CAB files for Device Projects", walks through the process of using CabWiz and modifying the INF files.This can be pretty tricky, so an easier alternative is to add the native DLL to your .NET assembly project as a content file. Keep in mind, this will only work if you're dealing with one platform and build type.

Conclusion

After reading this article, you'll have a good idea of how the Compact Framework handles, or rather doesn't handle, keyboard focus. As I've shown, with the Compact Framework MessageWindow class and a little helper native code, you can subclass Compact Framework controls, and intercept Windows CE messages. Then, by intercepting the WM_KEYDOWN message, you can add standard keyboard navigation to Compact Framework-based apps that run on the Pocket PC.

If you're like me, however, what you really want is some code to help make your life easier. I've provided four sample projects accompanying this article: PPCKeyboardSample1, PPCKeyboardSample2, CFTools and CFNative. PPCKeyboardSample1 shows how to add basic keyboard support (without tab key support) to Compact Framework-based applications using only the built-in Control.KeyDown event. PPCKeyboardSample2 shows how to add full keyboard support to a Compact Framework-based application using subclassing, and demonstrates the processes of form-level keyboard handling and exception marshaling. PPCKeyboardSample2 includes the library project CFTools, which in turn uses the native DLL project called CFNative.

The CFTools library project contains both the Subclasser and FocusControls classes discussed in this article. These classes can be used as-is in any Compact Framework-based application. To use these classes, you simply have to build the CFTools assembly and the supporting CFNative DLL. Then, add a reference to the CFTools DLL and make sure the native CFNative.DLL is in the path on the target device.

Alan Pulliam is a software architect with over 14 years of industry experience. He resides in Atlanta and is currently working as an independent consultant. He can be contacted at apulliam@iname.com.