Keyboard Navigation on PocketPC with the Compact Framework

We recently had a customer let us know that the difference between keyboard navigation on PocketPC and Smartphone was causing them trouble.  I thought I would take little time to work up a workaround for this issue.  I'll present a solution using the recommended managed methods first and then an alternative which plays with native interop to the SendInput() API.

The basic problem is that the direction pad (d-pad) can be used to navigate through a form of controls on Smartphone but not on PocketPC.  The d-pad on both devices act like the arrow keys of a standard keyboard and sends VK_UP, VK_DOWN, VK_LEFT and VK_RIGHT inputs to the system when used.  ComboBox, DateTimePicker and TextBox are examples of controls which capture and respond to the up and down arrows on PocketPC (Tab and Shift-Tab still navigate as expected and stylus input to the touchscreen, if present, will switch focus between controls).

The recommended solution is to hook the KeyDown event (or override OnKeyDown) to capture Keys.Down and Keys.Up and use the SelectNextControl() method on the Control class (which calls the same code as the internal tabbing navigation) to navigate to the next/previous control in the tab order.

Here's an excerpt from SmartphoneStyleNavigationWithSNC.zip which contains an instance of each of the troublesome controls, ComboBox, DateTimePicker and TextBox:

// Excerpted from InitializeComponent() in SampleForm.Designer.cs

this.comboBox1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.HandleKeyDown);
this.dateTimePicker1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.HandleKeyDown);
this.textBox1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.HandleKeyDown);

// Custom code in SampleForm.cs

private void HandleKeyDown(object sender, KeyEventArgs e)
{
    switch (e.KeyData)
{
        case Keys.Up:
        case Keys.Down:
e.Handled = true;
            this.SelectNextControl((Control)sender, e.KeyData == Keys.Down, true, true, true);
            break;

        default:
            break;
}
}

Something to note above is that we can call SelectNextControl() directly because HandleKeyDown() is implemented on a form, a top level window. If you decide to create custom control classes from ComboBox, DateTimePicker and/or TextBox which override OnKeyDown, you will need to use the following code:

this.TopLevelControl.SelectNextControl((Control)sender, e.KeyData == Keys.Down, true, true, true);

Just for kicks I looked into eating the arrow key messages and putting [Shift-]Tab key messages into the message queue. There's an official API, SendInput(), to do this. Here are the relevant parts of that code (note that you still need to hook the KeyDown events of the controls in question to HandleKeyDown):

public class WinCE
{
// The unused private fields below are required to match the native structure layout.
#pragma warning disable 0169
public struct KEYBOARDINPUT
{
public uint type;
public ushort wVk;
ushort wScan;
public uint dwFlags;
uint time;
uint dwExtraInfo;
uint unused1;
uint unused2;
}
#pragma warning restore 0169

public const uint INPUT_KEYBOARD = 1;
public const uint KEYEVENTF_KEYUP = 2;
public const ushort VK_TAB = 0x0009;
public const ushort VK_SHIFT = 0x0010;

[DllImport("coredll.dll", SetLastError = true)]
public static extern uint SendInput(uint cInputs, /* [MarshalAs(UnmanagedType.LPArray)] */ KEYBOARDINPUT[] inputs, int cbSize);
}

private void HandleKeyDown(object sender, KeyEventArgs e)
{
WinCE.KEYBOARDINPUT[] inputs;
uint retVal;
int error;

switch (e.KeyData)
{
case Keys.Down:
e.Handled = true;
inputs = new WinCE.KEYBOARDINPUT[2];
inputs[0].type = inputs[1].type = WinCE.INPUT_KEYBOARD;
inputs[0].wVk = inputs[1].wVk = WinCE.VK_TAB;
inputs[1].dwFlags = WinCE.KEYEVENTF_KEYUP;
retVal = WinCE.SendInput(2, inputs, 0x001C);
if (retVal != 2)
{
error = Marshal.GetLastWin32Error();
throw new Exception(string.Format("SendInput() returned {0}.", error));
}
break;

case Keys.Up:
e.Handled = true;
inputs = new WinCE.KEYBOARDINPUT[4];
inputs[0].type = inputs[1].type = inputs[2].type = inputs[3].type = WinCE.INPUT_KEYBOARD;
inputs[0].wVk = inputs[3].wVk = WinCE.VK_SHIFT;
inputs[1].wVk = inputs[2].wVk = WinCE.VK_TAB;
inputs[2].dwFlags = inputs[3].dwFlags = WinCE.KEYEVENTF_KEYUP;
retVal = WinCE.SendInput(4, inputs, 0x001C);
if (retVal != 4)
{
error = Marshal.GetLastWin32Error();
throw new Exception(string.Format("SendInput() returned {0}.", error));
}
break;

default:
break;
}
}

This posting is provided "AS IS" with no warranties, and confers no rights.

SmartphoneStyleNavigation.zip