Wednesday, July 06, 2005

Update: Tab Order

Previously, I posted about tab order problems with custom controls.

I found more information about it in the .NET Compact Framework Developer's Guide (here), which says,

To tab out of the custom control to the previous control, call this.Parent.Controls(this.Parent.GetChildIndex(customcontrol) - 1).Focus() in the KeyDown event handler when a Keys.Up key is detected.

I also found this exact phrasing in the Smart Client Developer Center Home: Frequently Asked Questions (link).

5.41. How do I tab out of a custom control to the previous control?
Call this.Parent.Controls(this.Parent.GetChildIndex(customcontrol) - 1).Focus() in the KeyDown event handler when a Keys.Up key is detected.

There are multiple glaring errors with this documentation, which I find completely unsatisfactory. The code they meant to write was something more along the lines of:

this.Parent.Controls[this.Parent.Controls.GetChildIndex(customcontrol) - 1].Focus() ;

1. this.Parent.Controls returns a ControlCollection which is accessed with brackets, not parenthesis
2. this.Parent.GetChildIndex does not exist, they meant to write this.Parent.Controls.GetChildIndex

Even given these two corrections, this code is still not entirely accurate. We really don't want to try and give focus to just any Control -- in fact, some controls can't be focused (e.g. panels, labels, etc.) so using their code, in some cases, we end up with nothing being focused at all. To fix this, we could change this code to something more like this:

int index = this.Parent.Controls.GetChildIndex(customcontrol);
do{
if(index == 0)
index = this.Parent.Controls.Count; //roll over
} while(!this.Parent.Controls[--index].CanFocus());
this.Parent.Controls[index].Focus();

However, CanFocus() is not supported by the .NET Compact Framework, so instead we have to do something like this:

int index = this.Parent.Controls.GetChildIndex(customcontrol);
do{
if(index == 0)
index = this.Parent.Controls.Count; //roll over
} while(!this.Parent.Controls[--index].Focus());

In the above code, we rely on the fact that the Control.Focus method returns false if the focus request was unsuccessful to find a Control that is focusable.

Update July 6th 2005, 2:00PM: Hmm, actually, as it turns out the Control.Focus() method returns true for Controls like Label -- which is somewhat contradictory to API documentation. So, I had to change this slightly. Here is some OnKeyDown code that I wrote for the SmartPhone that provides tab control for custom controls (sorry about the formatting, if I keep writing code on this blog, I will have to get a tool that simplifies posting code and formats it nicely as well).

protected override void OnKeyDown(KeyEventArgs e){
switch (e.KeyCode)
{
case Keys.Down:
int index = this.Parent.Controls.GetChildIndex(this);
do{
if ((index + 1) == this.Parent.Controls.Count)
index = -1; //roll over
index++;
} while (!isFocusable(this.Parent.Controls[index]));
break;

case Keys.Up:
index = this.Parent.Controls.GetChildIndex(this);
do{
if (index == 0)
index = this.Parent.Controls.Count; //roll over
index--;
} while(!isFocusable(this.Parent.Controls[index]));
break;
}
base.OnKeyDown(e);
}

public bool isFocusable(Control control)
{
if(control is Label)
return false;
else
return true;
}

No comments: