Microsoft’s programming tools for one-handed navigation may be designed around use of the Tab key, but I personally believe that the Tab key is not the best tool available for one-handed navigation. Why? Because many Windows Mobile devices lack a keyboard. Other devices may have a keyboard that slides out from underneath the device, meaning that the keyboard is not always available to the user. Still other devices have an always-available keyboard, but no Tab key (the Palm Treo is an example of a keyboard without a Tab key).
Note: |
|---|
|
The code in this article is provided to demonstrate one possible way to implement one-handed, stylus-free navigation support. You will no doubt have other ideas of how to achieve similar results and in many cases you will likely find better ways to do so.
|
But all Windows Mobile devices have an Action key, usually located on the bottom center of the device. The Action key differs in design from device to device, but always allows the user to simulate pressing a keyboard left arrow, right arrow, up arrow, down arrow, and Enter key. This is the primary tool our users will employ for one-handed navigation.
Let’s start with our sample program, and see how the Action key works out of the box with the common controls we’ve used in our program. Start the program in your Windows Mobile emulator, and this time try to navigate each screen using the Action key on the emulator. As always, the program opens with initial focus given to the Edit tab. By clicking the right-side of the Action key, you can shift focus to the Add tab, and then move from the Add tab back to the Edit tab by clicking the left side of the Action key. That’s the extent of the functionality—there’s no way at the moment to use the Action key to move focus away from the tabs and to the controls on each tabbed page.
Try clicking on some of the controls in our sample program, then try using the Action key to shift focus to another control. For the most part, you’ll find that this doesn’t work: You cannot shift focus away from the drop-down boxes, the text boxes, or the list box using the Action key. You can use the Action key to shift focus from one radio button to the other, but you can’t use the Action key to shift focus from a radio button to a control outside of the panel containing the radio buttons.
In fact, only the button controls in our sample program seem to respond to the Action key to permit one-handed navigation from one control to the other. But if you look carefully, you’ll discover that even the button controls fail to fully cooperate with our efforts to implement one-handed, stylus-free navigation. As a test, click the Copy button on the Edit tab in our sample program, and then press the desktop computer's Tab key. Notice how the Tab key correctly shifts the focus from the Edit button to the Edit tab. Now, click the Edit button again to give this button focus, and this time click the bottom of the Action key. Note that the focus shifts this time, not to the Edit tab, but to the language drop-down box! What is going on here?
Clearly, we’re going to have to write some code to get the navigation control to work the way we want it to work.
Before we look at some coding techniques for the Action key, let’s look at what we can do inside of a control using the Action key. Here, the news is not so grim. If we give focus to a text box that has some text in it, then we can use the Action key to move the cursor through the text in the control. That’s not everything we want to accomplish inside of this control, but it’s a start. Similarly, if we give focus to a drop-down box, we can click the center of the Action key to open and close the drop-down box, and we can use the other Action key's directions to change the selected item in the combo box. In similar fashion, if we give focus to our list box on the Add tab, the Action key can be used to select different items in the list. We can use the Action key to shift focus from one radio button to the other, and one tab to the other.
Let’s take a look at what we can do with the Action key when the date-time control on the Add tab is given focus. Here, we should be pleased to see that Microsoft makes full use of the Action key. We can use the Action key's left or right actions to change which portion of the date (month, day, or year) is highlighted, and we can increase or decrease the selected item by using the Action key's up or down actions. We cannot use the Action key to display the date-time picker’s calendar view (the calendar that becomes visible when you click the down arrow at the right of the control), or to navigate away from the date-time picker, but the Action key does give us some good functionality within the date-time picker. We may want to enhance this functionality, but we can appreciate that this functionality exists from the outset.
Now that we’ve seen how much and how little has been provided to us by Microsoft, let’s explore some techniques we might try to enhance the use of the Action key in our sample program.
Form-Level Techniques
If you’re like me, you’ve probably accidentally clicked the Action key in Visual Studio design view. If you’ve done this, you’ve seen that the following code is automatically added to your form:
|
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if ((e.KeyCode == System.Windows.Forms.Keys.Up))
{
// Up
}
if ((e.KeyCode == System.Windows.Forms.Keys.Down))
{
// Down
}
if ((e.KeyCode == System.Windows.Forms.Keys.Left))
{
// Left
}
if ((e.KeyCode == System.Windows.Forms.Keys.Right))
{
// Right
}
if ((e.KeyCode == System.Windows.Forms.Keys.Enter))
{
// Enter
}
} |
The preceding code handles the five possible actions that a user can perform with the Action key. Better yet, it captures these actions at the form level, meaning that this code comes into play regardless of which control has the current focus on the form. This looks like it could be a useful place to start coding! Next, we need a method we can call to shift focus from one control to the other.
Luckily, Microsoft has provided us with such a method—the SelectNextControl method. This is a very powerful method for our purposes. The method takes five parameters:
-
The control where we’ll begin our search
-
A bool to indicate whether we’re moving forward or backwards from the base control
-
A bool to indicate whether we’ll ignore controls that have their TabStop properties set to false
-
A bool to indicate whether we should include nested child controls
-
A bool to indicate whether we should continue searching from the first control in the tab order after the last control has been reached
To utilize the SelectNextControl method, we’ll need to determine which control on our form has the current focus. This is easy to do in the full version of .NET, where we can look at the ActiveControl property of the current form. Sadly, this property is not available in the .NET Compact Framework. (Unfortunately, we’re going to encounter a number of properties and methods excluded from the .NET Compact Framework that would be useful in setting up one-handed navigation.) So, we’ll need to adopt a different approach. We can take advantage of the fact that each control is contained in a collection on the form—we can loop through this collection to see at any moment which control has the current focus. Then we can specify this control as the first parameter in the SelectNextControl method, and use the method to move focus to the next (or preceding) control.
Let’s see how this works, using our less complicated form (Form2) as our initial model. Open this form in design view, and then click the Action key. This should open up the code for Form2, and add a Form2_KeyDown event handler to the form. Modify the code for this form, so that it reads as follows:
|
private void Form2_KeyDown(object sender, KeyEventArgs e)
{
if ((e.KeyCode == System.Windows.Forms.Keys.Up))
{
// Up
}
if ((e.KeyCode == System.Windows.Forms.Keys.Down))
{
for (int i = 0; i < this.Controls.Count; i++)
{
if (this.Controls[i].Focused)
{
this.SelectNextControl(this.Controls[i], true, true, true, true);
break;
}
}
}
if ((e.KeyCode == System.Windows.Forms.Keys.Left))
{
// Left
}
if ((e.KeyCode == System.Windows.Forms.Keys.Right))
{
// Right
}
if ((e.KeyCode == System.Windows.Forms.Keys.Enter))
{
// Enter
}
} |
Note that once we locate the control with the current focus, we use the SelectNextControl method to move focus to the next control and then execute a break instruction. The break instruction exits the for loop once we’ve found the control with current focus, and moves focus to the next control. This is essential for our code, because we only want to run the SelectNextControl method once for every Action key press. If we didn't break from the loop, we might inadvertently advance the focus several times on the single Action key press.
Modify the code in Program.cs to make Form2 the startup form, run the program in your Pocket PC emulator, and confirm that you can navigate through the simple form using the Action key's down action. The Action key's down action passes focus from drop-down boxes to the buttons and back again, ignoring the multiline text box unless the ReadOnly property of the text box has been set to false. Ignore for the moment the fact that navigating through the form changes the values displayed in the drop-down boxes—we’ll take care of that problem in due time. Hooray! That looks like a big step in implementing one-handed, stylus-free navigation in Visual Studio. Time to celebrate yet?
Not yet. The code is not performing as well as it appears to perform. You can see this for yourself if you create code for the Action key's up action to go with our code for the Action key's down action. The only difference between the code for the Action key's up and down actions is in the second parameter for the SelectNextControl method: This parameter is set to true to cause focus to move forwards, and to false to cause focus to move backwards. Modify your Form2 code so that it reads like the following code:
|
private void Form2_KeyDown(object sender, KeyEventArgs e)
{
if ((e.KeyCode == System.Windows.Forms.Keys.Up))
{
for (int i = 0; i < this.Controls.Count; i++)
{
if (this.Controls[i].Focused)
{
this.SelectNextControl(this.Controls[i], false, true, true, true);
break;
}
}
}
if ((e.KeyCode == System.Windows.Forms.Keys.Down))
{
for (int i = 0; i < this.Controls.Count; i++)
{
if (this.Controls[i].Focused)
{
this.SelectNextControl(this.Controls[i], true, true, true, true);
break;
}
}
}
if ((e.KeyCode == System.Windows.Forms.Keys.Left))
{
// Left
}
if ((e.KeyCode == System.Windows.Forms.Keys.Right))
{
// Right
}
if ((e.KeyCode == System.Windows.Forms.Keys.Enter))
{
// Enter
}
} |
Run the code in the Windows Mobile emulator, and this time try to navigate through the form using the Action key's up action. All is well until you try to navigate from the Edit button, where you’ll pass focus to the multiline text box. How did this happen? The multiline text box is not set up as a tab stop, and we’ve set up the parameters for our SelectNextControl method so that we only pass focus to tab stop controls. What is going on here?
If you experiment a little bit, you’ll discover that the buttons on our sample form ignore the SelectNextControl method altogether. No matter what code you try, the Action key's down action will pass focus from a button control forward to the next control (in order of TabIndex) capable of receiving focus, regardless of the TabStop setting for that control. The Action key's up action will act in exactly the same way, but in reverse TabIndex order. If you remember our earlier discussion, this is the default behavior for the button control: The action key's down action always passes focus from the button control to the next control (in order of TabIndex), and the Action key's up action always passes focus from the button control to the previous control on the form.
So, our button controls fail to heed the instructions we’ve set up in our Form2_KeyDown method. You may have also noticed that our text box also ignores these instructions: Once the text box takes program focus, it holds onto this focus, paying no attention to our SelectNextControl method.
(Finally, if you ran the current code on our more complicated form, the one with the tab control, you would notice that you could not return focus to these tabs by using the Down navigation button.)
What we need here is a simple fix, a magic bullet to solve all of these problems. Surprisingly, there is such a quick fix: add the line “e.Handled = true” after each call to the SelectNextControl method, so that your code reads as follows:
|
private void Form2_KeyDown(object sender, KeyEventArgs e)
{
if ((e.KeyCode == System.Windows.Forms.Keys.Up))
{
for (int i = 0; i < this.Controls.Count; i++)
{
if (this.Controls[i].Focused)
{
this.SelectNextControl(this.Controls[i], false, true, true, true);
e.Handled = true;
break;
}
}
}
if ((e.KeyCode == System.Windows.Forms.Keys.Down))
{
for (int i = 0; i < this.Controls.Count; i++)
{
if (this.Controls[i].Focused)
{
this.SelectNextControl(this.Controls[i], true, true, true, true);
e.Handled = true;
break;
}
}
}
if ((e.KeyCode == System.Windows.Forms.Keys.Left))
{
// Left
}
if ((e.KeyCode == System.Windows.Forms.Keys.Right))
{
// Right
}
if ((e.KeyCode == System.Windows.Forms.Keys.Enter))
{
// Enter
}
} |
Run your code in the emulator. Success! The problems we noted earlier are all fixed. We’ve even fixed the other problem we mentioned earlier, that navigating through our form changed the values displayed in the drop-down boxes on our form. Cool! The instruction e.Handled = true has saved the day.
When you set e.Handled = true, you’re telling the Form class' default KeyDown event handler that your program has handled the KeyDown event and that you do not want the Form class to perform the default KeyDown event handling. Using e.Handled = true allows you to limit the KeyDown event handling to only that code that you specifically implement. This is a powerful and useful instruction, one that we’re going to use more than once before we finish writing our code.
Our next task is to take the navigation code we used for our simple form, and modify it for use in our more complicated form. The code must be modified, because (as we discussed earlier) the only control in the Form1 controls collection is the tab control itself – we’ll need to reach down to the controls collection on our two tabs in order to make our navigation work. This means that we must loop through two collections of controls: the collection on the tab control (representing our two tabbed pages), and the collection on each tab.
Note: |
|---|
|
There is actually a third Controls collection, the one on Form1 itself. In this case though, we don't need to work with the Form1 Controls collection because we're taking advantage of our a priori knowledge that the Form1 class has only one control—the tab control—and that we are working with the tab control directly.
|
I won’t bore you with a detailed discussion of how to set up the nested loops. Other than the addition of (1) instructions for navigating the tab control, and (2) a second for loop to examine the collection of controls on each tabbed page, the code for each tab page is the same as the code we used in Form2. Open Form1 in design view, and then click the Action key. This should open up the code for Form1, and add a Form1_KeyDown event handler to the form. Modify the code for this form, so that it reads as follows:
|
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if ((e.KeyCode == System.Windows.Forms.Keys.Up))
{
if (this.tabControl1.Focused)
this.SelectNextControl(this.tabControl1, false, true, true, true);
else
{
for (int i = 0; i < this.tabControl1.Controls.Count; i++)
{
if (this.tabControl1.SelectedIndex == i)
{
for (int j = 0; j < this.tabControl1.Controls[0].Controls.Count; j++)
{
if (this.tabControl1.Controls[0].Controls[j].Focused)
{
this.SelectNextControl(this.tabControl1.Controls[0].Controls[j], false, true, true, true);
e.Handled = true;
break;
}
}
break;
}
} }
}
if ((e.KeyCode == System.Windows.Forms.Keys.Down))
{
if (this.tabControl1.Focused)
this.SelectNextControl(this.tabControl1, true, true, true, true);
else
{
for (int i = 0; i < this.tabControl1.Controls.Count; i++)
{
if (this.tabControl1.SelectedIndex == i)
{
for (int j = 0; j < this.tabControl1.Controls[0].Controls.Count; j++)
{
if (this.tabControl1.Controls[0].Controls[j].Focused)
{
this.SelectNextControl(this.tabControl1.Controls[0].Controls[j], true, true, true, true);
e.Handled = true;
break;
}
}
break;
}
}
}
}
if ((e.KeyCode == System.Windows.Forms.Keys.Left))
{
// Left
}
if ((e.KeyCode == System.Windows.Forms.Keys.Right))
{
// Right
}
if ((e.KeyCode == System.Windows.Forms.Keys.Enter))
{
// Enter
}
} |
Test this code in the Windows Mobile emulator – remember to change the startup form for the sample program from Form2 to Form1. You should be able to navigate backwards and forwards through the controls on both tabs, using the Action Keys up and down actions.
We’re left with one remaining problem: We still can’t navigate successfully to and from the radio buttons on the Add tab. The problem appears to be with the panel control that contains the radio buttons, so the logical next step is to eliminate the panel control while retaining the radio buttons. Go ahead and do this in the sample program: In form view, switch to the Add tab, drag the two radio buttons out of the panel control, delete the panel control, and reposition the buttons. Make sure that one of the buttons has its Checked property set to true. You may also need to open the Add tab in Tab Order view and re-order the controls so that their TabIndex properties increase as you move down the form. The revised Add tab should look like that shown in Figure 4:
Note: |
|---|
|
If moving controls outside of their parent control as we did with the radio buttons is not an option, you can modify the code that locates the active control so that the code also searches within any container controls, such as a Panel control, instead of searching only those controls that are placed directly on the Form or Tab control.
|
Figure 4. Form containing a tab control with multiple tabs displayed in Visual Studio's tab view
Run the program and try navigating through the controls on the Add tab, using the Tab key, and also the Action key's up and down actions. Now everything works! You can use the TAB key or the Action key's up and down actions to shift focus to and from the selected radio button, and you can use the Action key's left and right actions to change the selected radio button. Whew!
Control-Level Techniques
Up to this point, we have had good success using the KeyDown event and the SelectNextControl method on a form level. Why not stop here, pat ourselves on the back, and proclaim our sample program to be a complete realization of one-handed navigation?
Well, truth is, we’re about half-way there. We can now shift focus to every control in our sample program, using one-handed, stylus-free techniques. But we still want to fully utilize each control in a one-handed, stylus-free way, once the control receives focus.
Remember the rules we set forth at the beginning of this article? We want to do things like open our controls using the Enter key, or select text in a text box by shifting focus to the text box. The Action key's left and right actions are supposed to navigate away from list boxes and combo boxes, not change the item selected in these controls. The truth is, the program as currently implemented gives us some ability to utilize our controls in a one-handed way, but this ability is not yet complete.
We’ll spend a little bit of time talking about control-level techniques for three of our control types in our sample program: text boxes, combo boxes, and the date-time picker. The techniques we use to manipulate these controls should be applicable to the code you’ll write to handle most other types of controls. I’ll leave it to you to figure out how to write control-level techniques for list boxes, list views, tab controls, and the like.
Text Boxes
Take a moment to review the rules we set forth earlier for one-handed navigation of text boxes. (1) When you navigate to a text box using one-handed techniques, the text in the text box should be selected. (2) The Action key's up and down actions are supposed to navigate away from a text box in all events, but (3) the behavior of the Action key's left and right actions is supposed to depend on whether there is text in the text box, and the position of the cursor in this text. At the moment, the text boxes in our sample program satisfy criteria (2), but criteria (1) and (3) are not implemented.
How should we implement the automatic selection of text in a text box? Ideally, we would include the code for this automatic selection in our Form1_KeyDown method, along with all of our other navigation code. While this can probably be done, the code for doing so is complicated: It requires us to determine not only the type of object we are navigating away from, but also the type of object to which we are navigating. For example, if we’re using the Add tab of our sample program, when we navigate forward from the radio buttons, we’d need to check if the next control is a text box (which it is), then select the text in that text box. However, we’d have to make this check upon each navigation up and down in our program, even though we have only three text boxes in the program. That seems wasteful to me. It seems more efficient to run our automatic text selection code only when it is a text box that is about to receive focus—that way, the automatic text selection code would only run when we need it to run.
So… we've decided to implement the automatic selection of text in our text boxes by wiring a GotFocus event for these text boxes that selects all of the text in the text box each time the text box receives focus. The code we want to run when this event is fired would look like the following:
|
this.textBox1.SelectAll() |
So long as we stick to one-handed, stylus-free navigation, this code works just fine. But remember, our code needs to work well with both one-handed, stylus-free navigation and conventional stylus-based navigation. And if you experiment, you’ll find that wiring the SelectAll method to the GotFocus event can produce bizarre results when you use the stylus for navigation. If you set up a text box the way we’ve just described, and if there’s text in the text box, clicking the stylus in the middle of the text causes the SelectAll method to select part of the text but not all of it. You end up with a selection that looks something like that shown in Figure 5:
Figure 5. Result of selecting a text box using a stylus when the control forces a SelectAll on GotFocus
The text before your cursor is selected, and the text after your cursor is not selected. No one wants to see that.
How do you solve this problem? The key is to investigate where the cursor gets positioned when a text box receives focus. If the text box receives focus by means of a stylus click, then the cursor will be located at the point where the stylus touches the screen. But if the text box receives focus by means of one-handed navigation, then in most cases the cursor will be located at the beginning or at the end of the text. So we can test for the cursor location in our function, and select text only if the cursor is at the text beginning or text end. To implement this test, substitute the following code for the code you created for the GotFocus events for the three text boxes on Form1 (substituting the correct name of the text box control for the name shown in the code below):
|
if (this.textBox1.SelectionStart == 0 || this.textBox1.SelectionStart == this.textBox1.Text.Length)
this.textBox1.SelectAll(); |
Next, let’s tackle the operation of the Action key's left and right actions. Remember, if there’s no text in a text box, then the Action key's left and right actions are supposed to navigate away from the text box. If there is text in the text box, then the Action key's left and right actions are supposed to move the cursor left and right through the text, navigating away from the text box only if the cursor is at the beginning or end of the text. How are we supposed to make this work?
Go back and take a look at the code we’ve written so far for the Form1_KeyDown function. We have not yet written any code for the Keys.Left and Keys.Right portions of the code. Also, the code we have written (for Keys.Up and Keys.Down) makes no distinction between types of controls. The code we now need to write must be aware of the type of control that has the current focus—for example, we only want to apply our text box rules when it is a text box that has the focus.
For this purpose, we’ll take advantage of the GetType method that’s available to determine the type of any control. The Name property of the GetType method returns a string identifying whether the control is a “TextBox,” a “ComboBox,” or some other type of control. With this method, we can apply different coding instructions depending on the type of control that has the current focus.
Modify the Keys.Left and Keys.Right if statements currently in the Form1_KeysDown function, as shown in the following code:
|
if ((e.KeyCode == System.Windows.Forms.Keys.Left))
{
if (!this.tabControl1.Focused)
{
for (int i = 0; i < this.tabControl1.Controls.Count; i++)
{
if (this.tabControl1.SelectedIndex == i)
{
for (int j = 0; j < this.tabControl1.Controls[0].Controls.Count; j++)
{
if (this.tabControl1.Controls[0].Controls[j].Focused)
{
switch (this.tabControl1.Controls[0].Controls[j].GetType().Name)
{
case "TextBox":
// add text box code
break;
case "ComboBox":
// add Combo box code
break;
case "ListBox":
// add list box code
break;
}
break;
}
}
break;
}
}
}
}
if ((e.KeyCode == System.Windows.Forms.Keys.Right))
{
if (!this.tabControl1.Focused)
{
for (int i = 0; i < this.tabControl1.Controls.Count; i++)
{
if (this.tabControl1.SelectedIndex == i)
{
for (int j = 0; j < this.tabControl1.Controls[0].Controls.Count; j++)
{
if (this.tabControl1.Controls[0].Controls[j].Focused)
{
switch (this.tabControl1.Controls[0].Controls[j].GetType().Name)
{
case "TextBox":
// add text box code
break;
case "ComboBox":
// add Combo box code
break;
case "ListBox":
// add list box code
break;
}
break;
}
}
break;
}
}
}
} |
With this structure in place, we can now consider the code we want to run when a text box has focus. What we want to do is navigate away from the text box if no text is selected, the cursor is at the beginning or the end of the text, and the user selects the Action keys left or right actions. We can use the SelectedText and SelectionStart properties of the text box to determine the selected text and the cursor position… only at this point, we do not have a reference to the text box object. We only have a reference to a control object that happens to be a text box. So, we first need to cast our control object into a text box object, and then we can use the properties of the text box object to determine if we should navigate away from the text box control.
The code we need for our Keys.Left text box code is the following:
|
TextBox _tBox = (TextBox)this.tabControl1.Controls[0].Controls[j];
if (_tBox.SelectedText == "" && _tBox.SelectionStart == 0)
{
this.SelectNextControl(this.tabControl1.Controls[0].Controls[j], false, true, true, true);
e.Handled = true;
}
break;
|
And our Keys.Right text box code is the following:
|
TextBox _tBox = (TextBox)this.tabControl1.Controls[0].Controls[j];
if (_tBox.SelectedText == "" && _tBox.SelectionStart == _tBox.Text.Length)
{
this.SelectNextControl(this.tabControl1.Controls[0].Controls[j], true, true, true, true);
e.Handled = true;
}
break;
|
Note that we only needed to provide code for the event where the cursor is at the beginning or end of the text in the text box, and where we wanted to move focus to a different control. We did not need to write code to cover when the cursor is supposed to move backwards or forwards through the text. The text box provides this functionality by default.
Combo Boxes
Let’s quickly review how one-handed navigation should work for combo boxes. Clicking the Enter button should open or close the combo box. If the box is closed, then the directional navigation buttons should move focus to the previous or next control. If the combo box is open, then the Action key's up and down actions should change the selected item, and the Action key's left and right actions should save the selected item and move to the previous or next control.
Note that in our sample program, the combo box control handles the Enter button in the desired manner, but the other Action key behaviors do not satisfy our specifications. The Action key's up and down actions always navigate away from the combo box—we only expect to see such navigation when the combo box is closed. The Action key's left and right actions always change the combo box’s selected item, even when the combo box is closed. According to our specifications, it should be possible to change the selected item only if the combo box is open, and then only with the Action key's up and down actions.
But these specifications are not written in stone. In our application, it’s useful to be able to change the selected quotation in the Quote combo box without actually opening the combo box. If open, the Quote combo box obscures the full text of the quotation as shown in Figure 6.
Figure 6. Text box obscured by the expanded combo box
So for this application, we’ll bend the rules a little bit, as Microsoft seems to be inclined to allow us to do. We’ll follow the specifications, except that we’ll allow the Action key's left and right actions to change the combo box selected item when the combo box is closed. Admittedly, this might confuse our users, as it reverses the functions performed by the Action key's up and down actions and the Action key's left and right actions, depending on whether the combo box is open or closed (closed box: left/right changes selected item, up/down navigates to another control; open box: left/right navigates to another control, up/down changes selected item). We could revise this specification so that, for example, the Action key's up and down actions always changed the combo box selected item, regardless of whether the combo box was open or closed. However, this change would interfere with the code we’ve written earlier, which consistently allows the user to navigate from control to control using the Action key's up and down actions.
Ultimately, when it comes to implementing one-handed navigation for our more complicated controls, we have to make design choices and design compromises. After all, the Action key only gives us five options to work with! You may not agree with the choice I’ve made for combo boxes in our sample application, but the important point is that you’re required to make a choice. You should follow the specifications detailed earlier in this article whenever you can, and deviate from these specifications only when it makes sense to do so.
OK. Now that we’ve settled on how we’d like our combo boxes to behave in our sample application, let’s turn our focus back to the application code. Before we make any changes to our code, we should realize that our sample program already has most of the functionality we want to implement. We can open a combo box by giving the box focus and using the Enter key. We can change the selected item in a closed combo box by using the Action key's left and right actions, and we can navigate away from a closed combo box by using the Action key's up and down actions. The only functionality we’re lacking is when the combo box is open. For open combo boxes, (1) we want the Action key's up and down actions to change the selected item, and not to navigate away from the box, and (2) we want the Action key's left and right actions to navigate away from the combo box.
So… the first thing we need to be able to do is determine whether a combo box is open or closed. While .NET provides a DroppedDown property for combo boxes, this property is not available in .NET Compact Framework. So we’re forced to rely on Windows Messaging to determine the state of our combo boxes. The following code will do the trick:
|
const int CB_GETDROPPEDSTATE = 0x157;
ComboBox _cBox = [the combo box control we want to target];
Message comboBoxMessage = Message.Create(_cBox.Handle, CB_GETDROPPEDSTATE, IntPtr.Zero, IntPtr.Zero);
MessageWindow.SendMessage(ref comboBoxMessage);
// if isDropped is true, then the combo box is open
bool isDropped = comboBoxMessage.Result != IntPtr.Zero;
|
This code sets up a bool, isDropped, that will tell us whether a particular combo box is open or closed. Whether the combo box is open or closed is determined by simply sending the Win32® Windows Message, CB_GETDROPPEDSTATE, to the combo box. You’ll need to add a reference in your sample program to "Microsoft.WindowsCE.Forms" in order for this code to work.
Let’s test the code on the Action key's down action. At the moment, the Action key's down action always navigates away from the control having current focus. Let’s modify the code for this action so that if a combo box has the current focus, we navigate away from the combo box only if it is closed. This will require us to use the Windows Messaging code shown earlier, plus the ability to distinguish between types of controls that we demonstrated earlier for the text box control. Add the using Microsoft.WindowsCE.Forms statement to the form in your sample code, and then modify the existing code for the Keys.Up and Keys.Down KeyDown events with the following:
|
if ((e.KeyCode == System.Windows.Forms.Keys.Up))
{
if (this.tabControl1.Focused)
this.SelectNextControl(this.tabControl1, false, true, true, true);
else
{
for (int i = 0; i < this.tabControl1.Controls.Count; i++)
{
if (this.tabControl1.SelectedIndex == i)
{
for (int j = 0; j < this.tabControl1.Controls[0].Controls.Count; j++)
{
if (this.tabControl1.Controls[0].Controls[j].Focused)
{
switch (this.tabControl1.Controls[0].Controls[j].GetType().Name)
{
case "ComboBox":
ComboBox _cBox = (ComboBox)this.tabControl1.Controls[0].Controls[j];
Message comboBoxMessage = Message.Create(_cBox.Handle, 0x0157, IntPtr.Zero, IntPtr.Zero);
MessageWindow.SendMessage(ref comboBoxMessage);
bool isDropped = comboBoxMessage.Result != IntPtr.Zero;
if (!isDropped)
{
this.SelectNextControl(this.tabControl1.Controls[0].Controls[j], false, true, true, true);
e.Handled = true;
}
break;
default:
this.SelectNextControl(this.tabControl1.Controls[0].Controls[j], false, true, true, true);
e.Handled = true;
break;
}
}
}
break;
}
}
}
}
if ((e.KeyCode == System.Windows.Forms.Keys.Down))
{
if (this.tabControl1.Focused)
this.SelectNextControl(this.tabControl1, true, true, true, true);
else
{
for (int i = 0; i < this.tabControl1.Controls.Count; i++)
{
if (this.tabControl1.SelectedIndex == i)
{
for (int j = 0; j < this.tabControl1.Controls[0].Controls.Count; j++)
{
if (this.tabControl1.Controls[0].Controls[j].Focused)
{
switch (this.tabControl1.Controls[0].Controls[j].GetType().Name)
{
case "ComboBox":
ComboBox _cBox = (ComboBox)this.tabControl1.Controls[0].Controls[j];
Message comboBoxMessage = Message.Create(_cBox.Handle, 0x0157, IntPtr.Zero, IntPtr.Zero);
MessageWindow.SendMessage(ref comboBoxMessage);
bool isDropped = comboBoxMessage.Result != IntPtr.Zero;
if (!isDropped)
{
this.SelectNextControl(this.tabControl1.Controls[0].Controls[j], true, true, true, true);
e.Handled = true;
}
break;
default:
this.SelectNextControl(this.tabControl1.Controls[0].Controls[j], true, true, true, true);
e.Handled = true;
break;
}
break;
}
}
break;
}
}
}
}
|
Run this code on your Windows Mobile emulator. You’ll see that when a combo box has focus, the Action key's up and down actions only navigate to the next control if the combo box is closed. That’s good! Next, let's change the function of the Action key's left and right actions, so that they'll navigate to the next control if the combo box is open. Modify the existing code for the Keys.Left and Keys.Right KeyDown events with the following:
|
if ((e.KeyCode == System.Windows.Forms.Keys.Left))
{
if (!this.tabControl1.Focused)
{
for (int i = 0; i < this.tabControl1.Controls.Count; i++)
{
if (this.tabControl1.SelectedIndex == i)
{
for (int j = 0; j < this.tabControl1.Controls[0].Controls.Count; j++)
{
if (this.tabControl1.Controls[0].Controls[j].Focused)
{
switch (this.tabControl1.Controls[0].Controls[j].GetType().Name)
{
case "TextBox":
TextBox _tBox = (TextBox)this.tabControl1.Controls[0].Controls[j];
if (_tBox.SelectedText == "" && _tBox.SelectionStart == 0)
{
this.SelectNextControl(this.tabControl1.Controls[0].Controls[j], false, true, true, true);
e.Handled = true;
}
break;
case "ComboBox":
ComboBox _cBox = (ComboBox)this.tabControl1.Controls[0].Controls[j];
Message comboBoxMessage = Message.Create(_cBox.Handle, 0x0157, IntPtr.Zero, IntPtr.Zero);
MessageWindow.SendMessage(ref comboBoxMessage);
bool isDropped = comboBoxMessage.Result != IntPtr.Zero;
if (isDropped)
{
this.SelectNextControl(this.tabControl1.Controls[0].Controls[j], false, true, true, true);
e.Handled = true;
}
break;
case "ListBox":
// add list box code
break;
}
break;
}
}
break;
}
}
|