Post 9, The Rant Continues: TabIndex is Terrible. Windows Mobile needs "Smart" Key Navigation.

Yeah, so it's been well over 30 Days as I originally predicted, so I'm renaming this series 30 Posts of Bitching about .NET Compact Framework.

Windows Mobile "Standard" [0] device development is a pain. I've never owned one, because I can't really live without a touch screen.

I still fondly remember many years ago, when I first fired up the Visual Studio Form designer with the Smartphone form factor. I started started dragging my controls out, doing the usual, then I noticed something odd: I didn't have a Button control on my toolbox. I searched around for a bit, and assumed that my Visual Studio installation was hosed. I created a new Pocket PC project, and the Button control was there. So I dragged one out, and then changed the target platform to Smartphone. I reopened the form and saw this intimidating hazard sign:


BrokenSmartphone

Attempting to run the application resulted in a NotSupported exception. Apparently simple Button controls aren't supported on Windows Mobile Standard for whatever reason. I guess this is because there is no touch screen, and thus no mouse clicks, so they just dropped support for the Button control and left the developer with LinkLabels. That really brings application development to a grinding halt if you are trying to target both Smartphone and Pocket PC. [1]

Man, I've really strayed from the point of this article. Getting back on track: The purpose of this post is to discuss something I mentioned briefly in a previous post. One of the annoying issues associated with Smartphone development is that since you are restricted to only keyboard input, your application has to be very aware of key navigation. The user should be able to intuitively navigate through all the forms quickly and easily. However, from an API level, all the developer is given is TabIndex. TabIndex is a property that you can set on any control that let the Windows API know the tab order of controls. For example, the Up button on a Smartphone would take you back to the control with the previous TabIndex, while the Down button would take you to the next TabIndex.

All is well in the world of TabIndex and simple 5 minute applications. TabIndex works great if all your controls are laid out vertically, with one control per line. But if you need to do any real Windows Mobile application development, chances are that TabIndex will not cut it. Take for example the following Form:

TabIndexSucks

 

For visual reference, the yellow region is a panel housing 4 LinkLabels, and the white region is the Form housing the other 6. The TabIndex of each Link is shown.

Navigating through this form is gross. For one, the Left and Right keys don't actually navigate left or right to other Controls. And the Up and Down Keys simply go through the Controls in TabIndex order. So, if "Panel TabIndex 0" had focus, pressing up would take you to "Form TabIndex 5" and not "Form TabIndex 2". Conversely, if "Form TabIndex 2" had focus, pressing down would take you to "Form TabIndex 3" and not "Panel TabIndex 0".

Basically, once you introduce any sort of grid or panels, TabIndex probably will not work for you. At that point, you need to hook the KeyDown events and handle the navigation to and from each Control... manually... for every Form. And as development progesses, and the UI changes, you better make sure that you update all those nasty KeyDown event hooks so your navigation doesn't break!

This is not ideal; it introduces code bloat, bugs, and is in general just tedious to do. To that end, I wrote an algorithm that would analyze the Controls in a Form and figure out the best Navigation target from any given Control (if there was one).

I'm not going to really get into the algorithm in great detail. Basically the way it works is that it looks at the key that was pressed and finds the corresponding edge of the Control. It uses that edge to find the opposite edge of all the other controls that has the best "score". A score is comprised of 2 factors: how close are the edges to each other and how much of the edge is "shared". Yeah, that probably made no sense. Maybe this picture will help:

EdgeAlgorithm

 

So, yeah, Edge Distance and Shared Edge Length are the two weighting factors in determining the best navigation target.

This navigation code is actually a small part of the overall UI framework I use. I've stripped out the algorithm and repackaged it so that it can be used by anyone. Usage is pretty straightforward. The Navigation class has two members:

  • FindFocusedControl - Find the control on the Form that is currently focused.
  • GetBestNavigationTarget - Given a Control and a navigation Key (Up, Down, Left, or Right), find the best navigation target.

To catch navigation events easily, you can do it at a form level by enabling KeyPreview. Then override OnKeyDown and use those two methods to find the Control that should receive focus. For example, here's the code from the sample:

using System;
using System.Windows.Forms;

namespace KeyNavigationDemo
{
public partial class NavigationDemoForm : Form
{
public NavigationDemoForm()
{
InitializeComponent();

// By enabling key preview, the form receives all key events
// that would otherwise be received and processed by a control.
// We can choose to handle these events.
KeyPreview = true;
}

private void myExitMenuItem_Click(object sender, EventArgs e)
{
Close();
}

private void mySmartNavigationMenuItem_Click(object sender, EventArgs e)
{
mySmartNavigationMenuItem.Checked = !mySmartNavigationMenuItem.Checked;
}

protected override void OnKeyDown(KeyEventArgs e)
{
if (mySmartNavigationMenuItem.Checked)
{
switch (e.KeyCode)
{
case Keys.Up:
case Keys.Down:
case Keys.Right:
case Keys.Left:
{
Control focused = Navigation.FindFocusedControl(this);
if (focused != null)
{
Control navigateTarget = Navigation.GetBestNavigationTarget(focused, e.KeyCode);
e.Handled = true;
if (navigateTarget != null)
navigateTarget.Focus();
}
}
break;
}
}
base.OnKeyDown(e);
}
}
}

Here's the full source code to the navigation algorithm and the sample demonstrating how to use it. The sample application is the same one you see in the above image with 10 LinkLabels. By default, Smart Navigation is disabled, use the arrow keys to navigate around the form using TabIndex. Then enable Smart Navigation in the Menu and use the arrow keys to navigate intuitively.


The sample is just a demonstration of the navigation; keep in mind that some Controls may want to handle certain key presses. For example, text boxes should generally receive the left and right key presses to navigate within the entered text and not to another Control.


[0] "Standard" is the official Microsoft term for a Windows Mobile non touch screen device. Professional is the term for touch screen devices. It used to go by "Smartphone" and "Pocket PC" back in 2003, and then they changed it to Standard and Professional, so that's how it is now. I think. Don't quote me. In fact, forget you ever read this.


[1] I should mention, there is nothing to stop a developer from basically inheriting from Control and overriding OnClick and thus creating a Button control. This works just fine, and it is just benign code on a Smartphone; behind the scenes those mouse methods are just a result of receiving WM_LBUTTONDOWN/UP messages. So, I ended up creating my own ButtonBase class in the UI framework I wrote/use.

1 comments:

Mark said...

Tell me about it! I started developing a program for Windows Mobile Standard (no touchscreen) and discovered that they didn't even include a listbox (a simple listbox!!) control. I don't know what they were thinking. The smartphone seems to have a native listbox control, but not a managed one.