RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Exploring Secrets of .NET Keystroke Handling : Page 4

If you've ever been frustrated trying to figure out how to intercept or assign specific keystrokes to specific controls, you'll be glad you found this article.

Putting the Sandbox to Work
The previous section provided some isolated tutorial examples, but here's a more practical example. Figure 7 shows a series of experiments with the keystroke A, including both a key sequence and the control pane, because settings on the control pane affect the output. The focus is on the TextBox for each key sequence in the figure.

Figure 7. Consuming the Keystroke A with Focus on a TextBox: (1) The TextBox, by default, consumes the keystroke and displays it. (2) Keystroke Sandbox emulates consuming it via the ProcessDialogKey method at the Form level. (3) Consuming in ProcessCmdKey at the Form. (4) Consuming in ProcessDialogKey at the TextBox. (5) Consuming in ProcessCmdKey at the TextBox.

Sequence 1 processes the keystroke without any manipulation by Keystroke Sandbox. The default behavior of a TextBox is to consume the character and display it. Observe that the process flow involves methods at both the Form and the TextBox. The TextBox control fires the KeyDown, KeyPress, and KeyUp events as it processes the character.

To generate the second sequence, set the control pane to the Form control (which is the default), then click on the arrow emanating from ProcessDialogKey. That adds the circle-and-slash symbol signifying that the ProcessDialogKey method will consume any keystrokes that reach it. Sequence 2 confirms this: click in the TextBox then press A; upon reaching ProcessDialogKey at the Form level (the sixth key panel), there is a red tick mark, indicating the keystroke is being consumed. Curiously, it still trickles through to the TextBox which fires a KeyUp event.

Figure 8. Revisions to the Processing Flow to Accommodate TextBox Experiments: The initial flowchart (left) did not quite hold up with experiments in Figure 7; the flowchart on the right shows revisions in red to align with the observed behavior.
In the third trial, consume the keystroke in ProcessCmdKey by clicking on the arrow following that method, refocus the TextBox by clicking in it, then press A. (You could turn on the flow out of ProcessDialogKey if you wish, but that does not matter as it will never be reached.) Again, this truncates most of the flow, but the KeyUp event still fires.

For the fourth and fifth sequences, reset the Form in the control pane so that all arrows allow flowthrough. Then switch the control pane to the TextBox and turn off the flow in ProcessDialogKey, then ProcessCmdKey, respectively.

Are these the results you should see? Figure 1 presented a first pass at a general diagram describing the flow. This is redrawn in Figure 8 as the leftmost flowchart. The experiments just run and diagrammed in Figure 7, though, do not quite mesh with that. If it were accurate, you should have seen KeyPress activity and KeyUp activity. The right hand flowchart in Figure 8 shows the revisions, adding red lines to match the experimental results from Figure 7.

The next series of experiments documented in Figure 9 again involve just a press of the A key but with a crucial difference: this time a CheckBox (any checkbox will do) has the focus when A is pressed rather than a TextBox. The first sequence in Figure 9 shows an unrestricted flow, so the A goes first through ProcessCmdKey and ProcessDialogKey on the Form. Because it's not handled, no KeyDown event occurs. It moves on to ProcessDialogChar and then to ProcessMnemonic—first for the Form, then for each child control. Recall that a mnemonic is a character you may assign to any control to make pressing a key act like clicking it with the mouse. Because no control registered the A character as a mnemonic, nothing is clicked.

Sequences 2 and 3 emulate having a mnemonic defined first at the Form and then at the TextBox, respectively. In each case, note that consuming the keystroke halts further propagation.

Figure 9. Consuming the Keystroke A with Focus on a CheckBox: (1) Normal handling—the .NET framework attempts to find a consumer of the keystroke but does not. (2) Keystroke Sandbox emulates consuming it via the ProcessMnemonic method at the Form level. (3) Consuming in ProcessMnemonic at the TextBox. (5) Consuming a real mnemonic key (G defined on the Load Grid button).
Figure 10. Revisions to the Processing Flow to Accommodate CheckBox Experiments: Neither flowchart in Figure 8 matches the experimental observations from Figure 9. This chart roughly accommodates them. The quest for a unified flowchart continues….
The final sequence in Figure 9 shows a real mnemonic (rather than simulating one via the Control Pane). Recall that Keystroke Sandbox defined a G mnemonic on the Load Grid button. Since that consumes the keystroke, ProcessMnemonic never gets called on any child controls.

This series of experiments again raises some questions about the accuracy of the keystroke flow in Figure 8. It does not correspond to either the original flow or the revision. Figure 10 shows what seems to happen with mnemonics.

Author's Note: In fact, I could not reconcile this data for the true mnemonic with that from the previous experiment. Apparently, there are some mechanisms involved that I have not yet come across. Eventually I would like to come up with a single, unified flowchart that covers all cases.

Extending the Keystroke Sandbox
The sandbox comes with three instrumented controls: the application itself, a TextBox and a DataGridView. If you want to add more, instrumenting your own controls is a simple three-step process:

  1. Create a new Control that inherits from the Control you are interested in. By convention, if the base control is Xyz, name your new control InstrumentedXyz. Make your new control implement the IInstrumentedControl interface. So your class so far would look like this (replace Xyz with the actual Control name):
  2.    public partial class InstrumentedXyz : Xyz, IInstrumentedControl
           #region IInstrumentedControl Members
           public string Nickname
               get { return "Xyz"; }
  3. Create a KeyDisplay instance that connects your class to the output facilities. Add a variable and initialize it in the constructor, for example:
  4.    KeyDisplay keyDisplay;
       public InstrumentedXyz()
           keyDisplay = new KeyDisplay(this);
  5. Finally, override the 10 relevant methods that need to connect to the sandbox. The code (see Listing 1) for these ten methods is always the same, so you can simply cut and paste it.

Close Icon
Thanks for your registration, follow us on our social networks to keep up-to-date