devxlogo

Exploring Secrets of .NET Keystroke Handling

Exploring Secrets of .NET Keystroke Handling

ew areas in .NET are seemingly as simple yet deceptively challenging as processing keyboard inputs. This situation is exacerbated because neither the MSDN documentation nor any of the excellent .NET support websites provide comprehensive, practical details about handling keystrokes. Enter Keystroke Sandbox, a small application developed just for this article. Keystroke Sandbox shows you graphically what happens when you press a single key or a combination of keys. Furthermore, it lets you customize its environment at runtime to emulate a variety of the most common Windows Forms application patterns, including both enabling/disabling controls to receive or ignore input as well as simulating consuming keystrokes at different stages and using different controls.

This article covers the different types of keyboard interaction that an application may have. You’ll see how you can simplify debugging by observing which methods get invoked on which controls when you press a key. The article concludes with a practical guide for implementing a variety of common key-handling scenarios.

What You Need
  • Visual Studio 2008
  • C# 3.0
  • .NET Framework: 2.0 or higher

Keystroke/ Program Interactions
Before considering the practical matter of wiring up key handlers in your code, Table 1 shows a handy reference guide to all the typical situations where your programs need to handle keyboard input (although there are certainly other, more abstruse situations where keyboard input would be required).

Table 1. Types of Keyboard Interactions: The table lists all the common types of keyboard/program interaction.
Element Character class Example or Details
Open a fixed menu
(i.e. on a menu bar)
Alt + alphanumeric Alt+F typically opens the file menu.
Mnemonic on a fixed menu item alphanumeric The File menu frequently contains an Exit choice, with the underlined key identifying a mnemonic key that activates that choice.
Mnemonic on a context menu item alphanumeric A mnemonic key defined on any menu item on a control’s context menu. Users open the context menu either by right-clicking or by pressing the Menu key on most modern keyboards.
Mnemonic on another control Alt + alphanumeric By defining a mnemonic key on a control such as a Button, users can click the button using Alt plus that mnemonic (or just the mnemonic depending on where the focus is)?just as if they had clicked it with the mouse.
Shortcut on a fixed menu item (modifiers) alphanumeric Shortcut keys are combinations that call a menu item’s underlying handler just as if a user had clicked that item. For example, in most applications the Edit menu defines Control+C as a shortcut key for the Copy menu item, Control+V as a shortcut for Paste, and Control+X for Cut.
Modify a mouse click (modifiers) Use this when you want to distinguish between an unadorned mouse click and a Control click (depressing Control while clicking with the mouse).
Echo a typed character (Shift) alphanumeric An example would be to display A when a user types Shift+A.
Inter-control Navigation Tab Move focus from one control in your application to the next in a predefined sequence.
Intra-control Navigation Navigational keys:
? Arrow keys
? Home/End
? Page Up/Page Down
Move the cursor appropriately in a TextBox, or move to a different cell in a DataGridView, etc.
Change program state Escape You would typically use Escape to let users close or cancel a dialog box or an ongoing operation.
Miscellaneous Space Typically used as a toggle. For example, you can use the Space key to toggle a checkbox.

Author’s Note: In Table 1, the term modifiers in the table refers to any combination of Shift, Alt, and Control. The terms shown in italics surrounded by parentheses in the Character Class column are optional. If you are unsure what the name of a particular key means, Wikipedia’s entry on the standard PC keyboard layout is a useful reference.

Hooking Keystrokes
To customize key handling in your application, you must be aware of the points where you may add hooks into the .NET framework. Table 2 lists the most common key-handling methods that you can override in any control. Note that this table includes both forms and controls, because a System.Windows.Forms.Form derives from Control just like a Button or a Label.

Table 2. Key-Handling Methods: Blue rows indicate methods that fire events. Salmon rows indicate the primary methods to override to alter key-handling behavior.
Method Purpose Override When?

Where Available

Form Form with preview Child
OnPreviewKeyDown Raises the PreviewKeyDown event. ? ? ? X
ProcessCmdKey Check for keys associated with a command You need custom shortcut handling unachievable with a MenuItem. X X X
IsInputKey Determine if a key should raise the OnKeyDown event You want a key that’s normally a navigational key to instead generate the OnKeyDown event. ? ? X
ProcessDialogKey Check for navigational keys (e.g. escape, tab, return, arrows, home/end, etc.) You want special input/navigational handling for your child controls X X X
OnKeyDown Raises the KeyDown event. ? ? X X
IsInputChar Determine if a key should raise the OnKeyPress event You want a key that’s normally a navigational key to instead generate the OnKeyPress event. ? ? X
ProcessDialogChar Check for navigational keys You need special navigational handling to occur on a key press X ? X
ProcessMnemonic Check for matching mnemonic on the current control. Writing a control that has special handling of mnemonics X ? ?
OnKeyPress Raises the KeyPress event. ? ? X X
OnKeyUp Raises the KeyUp event. ? ? X X

Author’s Notes: In Table 2:
  • Methods appear in the order in which they occur in a running program.
  • The MSDN reference page for each method listed may be accessed from this MSDN page on Control methods.
  • The “Override When?” column stems from jfo’s coding blog, an excellent reference on keyboard events.
  • The final three columns indicate where the methods are available, described next.

Despite the fact that the parent form and its children all derive from the Control class, you might assume that all the methods in Table 2 are available for both the parent form or its children. But the parent form has slightly different behavior, as noted by the three sub-columns under the “Where Available” column in Table 2. Available in this case means that the indicated method will be executed under some conditions if there is an “X” in the column. Those without an “X” will never be executed. The first column (labeled “Form”) applies to a form only when its KeyPreview property is disabled (False). The “Form w/preview” column applies to the form with its KeyPreview property enabled. The final “Child” column applies to any child control.

Interactions Among Key-Handling Methods
Again, the methods in Table 2 appear in the order they’re executed; however, just because the table shows a method as available does not mean that it is always executed. Figure 1 shows the somewhat complicated interactions among the various key-handling methods. The colors in this figure correspond to the colors used in Table 2: blue indicates methods to fire events and salmon indicates primary methods to alter behavior.

?
Figure 1. Processing Flow: An initial diagram of methods and events involved in keyboard interactions (per “jfo’s coding” blog), and how they may be altered. See Figure 8 for revisions to this flowchart.

I constructed the flowchart in Figure 1 based on the information in jfo’s coding blog. You will shortly learn how to actually observe the flow indicated in the figure using Keystroke Sandbox?and to see that it needs some revision!

Figure 1 shows that you can break a keystroke down into three principal actions: KeyDown, KeyPress, and KeyUp. Each of these actions wends its way toward a goal of firing an event of the same name (the blue boxes). However, it is by no means inevitable that an action results in firing its associated event?that depends on how you handle the methods that lead up to it, as you’ll see later.

Keystroke Sandbox: A Tool for Observing the Flow of Key-Handling Actions
Before moving on to the practical guide to handling keyboard input, you should be familiar with the Keystroke Sandbox (see Figure 2), a tool designed just for this article. Keystroke Sandbox visually shows which methods fire in which controls when you press one or more keys.

?
Figure 2. Keystroke Sandbox: Using this diagnostic tool you can observe the flow of key-handling activity through any instrumented controls.

Keystroke Sandbox Overview
Keystroke Sandbox displays key-handling activity for any instrumented control. By default, the application includes an InstrumentedTextBox and an InstrumentedDataGridView. You can very easily instrument any other control you wish, place it on the form, and it will automatically be integrated into the application (you’ll see more information later in the section “Extending the Keystroke Sandbox”). The form is also instrumented. Any key handling performed by any of these controls appear in the main output pane, modulated by any settings you have made in the control pane at the bottom of the window.

Using the Control Pane
To focus precisely on what you want to see, use the control pane at the bottom to modify the behavior of any instrumented control in the application.

First, select the control of interest in the drop down box at the left. All the other elements in the control pane (the checkboxes and arrows) will immediately update to reflect the settings for that control. Just below the drop down you have two checkboxes. The “Enable” checkbox enables or disables the referenced control on the fly. Disabled controls receive no keystrokes?they’re effectively turned off. The “Display” checkbox serves as a filter. When checked, the selected control’s activity appears in the main output pane; uncheck it to suppress output from that control.

The most interesting part of the control pane, however, is the graphical flow chart. The graph shows the four principal methods that provide customization of key handling, discussed in previous sections, along with the three key related events (using the same color coding as in Table 2 and Figure 1). Adding key-handling customization consumes a keystroke rather than letting it continue percolating through the system. By clicking on an arrow in the graph emanating from any of the four methods, you can instruct the application to emulate consuming the keystroke. Observe the standard circle-and-slash cross out on the first arrow emanating from ProcessCmdKey in Figure 2. Clicking the arrow again lets the keystroke pass. When a keystroke is consumed the output pane notes this by a short red tick between key panels; you can see one between the second and third key panels on the last line of the output pane in Figure 2.

What events are activated?and when?depends on a variety of factors in your application: which control has focus (and therefore the control must be enabled), what type of control, whether the form has KeyPreview enabled, whether the key combination represents a shortcut key, a mnemonic key, or a navigational key, etc. The sandbox therefore includes many commonly available controls (besides just the instrumented ones): checkboxes, combo boxes, buttons, labels, fixed menu items, and context menu items (on the textbox). Most of these therefore do double duty: they both change a setting in the application and also serve as components that have focus in the sandbox when you press a key. Table 3 is a quick reference guide to activate the element types?first introduced in Table 1.

Table 3. Triggering Scenarios in Keystroke Sandbox: The first two columns are identical to Table 1; however, the third column here shows how to trigger each of the elements in the sandbox.
Element Character class Keystroke Sandbox implementation
Open a fixed menu (i.e. on a menu bar) Alt + alphanumeric Alt+F opens the file menu.
Mnemonic on a fixed menu item alphanumeric The first item in the File menu, Fixed Menu Item, has its mnemonic set to X with no shortcut key set.
Mnemonic on a context menu item alphanumeric The TextBox has a context menu with two items, one with a mnemonic (M) and the other without. Once you open the context menu, then, pressing m will invoke the Context menu with mnemonic item. The other item defaults to an implicit mnemonic?the first character of the string?so pressing C will invoke Context menu without mnemonic.
Mnemonic on another control Alt + alphanumeric The Load Grid button has its mnemonic set to G, so pressing Alt+G will click the button.
Shortcut on a fixed menu item (modifiers) alphanumeric The second item in the File menu, Fixed Menu with shortcut, has its shortcut set to the Delete key?which provides a shortcut with just one key. The next item, Fixed Menu with compound shortcut, has a more conventional shortcut combination: Ctrl+B. The final menu item looks and acts almost the same, but actually uses a fake shortcut, to be discussed in the “how to” section towards the end of this article.
Modify a mouse click (modifiers) The Load Grid button loads the grid when clicked, but if you hold down Ctrl when you click it, it empties the grid instead.
Echo a typed character (Shift) alphanumeric You can type anything into the TextBox, when it’s enabled.
Intra-control Navigation Navigational keys:
? Arrow keys
? Home/End
? Page Up/Page Down
After typing several lines of text in the TextBox or loading the grid, you can click somewhere inside either control and use these standard navigational keys.
Inter-control Navigation Tab Click in the separation spinner, and then press the Tab key several times, pausing just briefly between each press. You’ll see different output each time as the focus changes from control to control.
Change program state Escape Open the file menu with Alt+F, and then press Escape to close it. You will see output when you open the menu but not when you close it!
Miscellaneous Space The spacebar toggles state in a CheckBox. Try giving the Key Preview box the focus (by clicking it), and then press the spacebar a couple of times to toggle the state and change the output.

Keystroke Sandbox: The Key Panel
Each time you invoke a key combination?a set of keystrokes that together comprise a single unit (e.g. Control+Alt+P)?Keystroke Sandbox emits a header and a key sequence. The header is a thick black line in the output pane containing the current settings:

  • A date/time stamp
  • Which control has focus
  • Whether the form’s KeyPreview is enabled
  • Which instrumented controls are disabled, if any
  • Which instrumented controls are suppressed, if any

The key sequence immediately follows the header and may wrap onto several lines, depending on your window width. The key sequence comprises a set of zero or more key panels. Each key panel represents the action of a single method of a single control. Table 4 provides these definitions for future reference. Figure 3 shows a collection of assorted key panels to point out what information you may glean from them. A single key panel is magnified towards the left half of the diagram. In the right half, I have artificially exposed a variety of tooltips to show you that, though some things are condensed to fit in the panel, the full information is available by hovering over each item with the mouse. (Each exposed tooltip corresponds to a component to the left and slightly higher than itself.)

Table 4. Key nomenclature: Here’s a quick reference to the terms I use in this article.
Term Meaning
Key combination The set of keys comprising a single keystroke, e.g. Control+Alt+R.
Key sequence A set of key panels corresponding to one key combination.
Key panel A proxy representing the action of a single method of a single control.

?
Figure 3. KeyPanel Details: On the left, a single key panel is enlarged and labeled. On the right, many tooltips have been artificially exposed to show how full information is available beyond the condensed information displayed.

The magnified key panel in Figure 3 itemizes the component parts of a key panel:

  • Control: This displays the truncated or abbreviated class name of the control responding to the key (abbreviated by omitting the prefix Instrumented,” if present). The full name is available as a tooltip. Each control is arbitrarily assigned a color that is used as the background color for this component. For example, in Figure 3 all key panels with the top component yellow are for the InstrumentedTextBox.
  • Method: This component displays the name of the method that handles the keystroke, truncated or abbreviated to fit the key panel by omitting the prefix Process, if present (as in ProcessCmdKey, ProcessDialogKey, etc.). The full name is available as a tooltip. Each key event (KeyDown, KeyPress, and KeyUp) has a correspondingly color-coded background for the methods associated with it. Thus, all OnKeyDown methods are teal, as are all ProcessDialogKey methods, because ProcessDialogKey is also associated with the KeyDown event.
  • Key name: This component holds the truncated or abbreviated name of the key. This is often obvious, but not always. If you press A, the name is “A.” If you press Control, the name is ControlKey (and the Key suffix is omitted). If you press Alt, though, the name is MenuKey. This field is overloaded: if the method is an existential method (e.g. IsInputKey), then this field contains the Boolean return value for that function (and is colored blue to differentiate it).
  • Modifiers: These are symbolic indicators of currently active modifier keys, if any. The possible values are Control (^), Shift (_), or Alt (~). A tooltip provides the words corresponding to these symbols.
  • ASCII value: This component is populated only when the key ends up as a real character, typically evident in the OnKeyPress method. Note that when an ASCII value is available, the key name component also has more resolution, in a sense. That is, in Figure 3 many of the key panels show a capital A even though a lowercase a was typed. That is because it is not a character at that point; it is a value from the Keys enumeration, specifically Keys.A. Only when it the value gets treated as a character can the case be distinguished.

Output Scenarios from Keystroke Sandbox
The screenshots in this section illustrate how to evaluate output from Keystroke Sandbox. Figure 4 and Figure 5 show the output for the same seven key combinations; however, Figure 4 displays key panels only for the form, while Figure 5 displays key panels only for the TextBox. For each figure, I used the control pane at the bottom of the application to suppress output from unwanted controls.

Author’s Note: Be cautious when masking output, because controls can interact with each other. It’s often helpful to see all the output, even though sifting through the longer output takes more effort.

?
Figure 4. Form-Level Key Sequences: Here are key panels for the Form level only; the TextBox and DataGridView key panels are hidden. The keypresses for each row are: (1) An ordinary character. (2) A menu shortcut. (3) A navigation key (right arrow). (4) An escape key. (5) A navigation key (home). (6) A mnemonic on the Load Grid button (Alt+G). (7) The Tab key.
?
Figure 5. TextBox Key Sequences: Here are key panels for the TextBox only; the Form and DataGridView key panels are hidden. The keypresses for each row are: (1) An ordinary character. (2) A menu shortcut. (3) A navigation key (right arrow). (4) An escape key. (5) A navigation key (home). (6) A mnemonic on the Load Grid button (Alt+G). (7) The Tab key.
?
Figure 6. Key Panel Sequence: The figure shows panels for a single key combination, Control-Shift-F, with Control key panels highlighted in red and Shift key panels in purple.

Both Figures 4 and 5, because they suppressed some output, produced relatively short key sequences. But take a look at Figure 6, the key sequence for a single key combination, Ctrl+Shift+F, which is rather lengthy.

Recall that there are three events to any keystroke: KeyDown, KeyPress, and KeyUp. Physically, in this example, Ctrl was pressed first, which invoked various methods by firing the KeyDown event. Pressing and holding the Shift key did likewise. Finally, adding the F key invoked KeyDown and KeyPress. I then released the keys: first F, then Shift, then Control (see the last six key panels); KeyUp fires as each key gets released. The order of the press and release of each key in the combination, therefore, affects whether all its key panels are contiguous.

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"; }       }              #endregion   }
  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()   {       InitializeComponent();       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.

Practical Techniques
Now that you’ve seen the details of how .NET handles keystrokes, and how you can use Keystroke Sandbox to examine what actually happens at runtime, you have a good foundation for using the practical techniques presented in the final portion of this article. These techniques will guide you in wiring up appropriate keystroke-handling mechanisms for specific use cases.

Bind a Key Combination to a Menu Item

?
Figure 11. Defining a Menu Item Shortcut: The figure shows the process of binding the key combination Ctrl+5 to the fixed menu item labeled “Fixed menu with shortcut.”

This is the most common approach for managing keystrokes, and Visual Studio makes it simple. With the designer open, select the menu item of interest, and then select ShortcutKeys in the Property pane. Click the drop-down arrow that appears, and then enter the shortcut key along with any desired modifiers. The shortcut you enter appears in the designer view of your form, as shown in Figure 11.

Note that just below the ShortcutKeys property you also have a ShowShortcutKeys property. That value must be set to True for the key combination to appear in the menu. One other useful property (not shown) is ShortcutKeyDisplayString. This optional property lets you override the default display for the key combination you have selected (not used in this example: Ctrl+5 is perfectly adequate). If, however, you wanted to display the keystroke combination Alt plus a period (Alt+.), the default display would show Alt+OemPeriod. Using ShortcutKeyDisplayString you could change this to a more user-friendly Alt+Period or Alt+dot or even Alt+. .

When you bind a key combination in this fashion, a user can simply press Ctrl+5 to invoke the menu item without ever having to open or access the menu at all. This is a quick and easy way to provide a set of keyboard commands to drive your program.

?
Figure 12. Autogenerating Overrides: Visual Studio Intellisense lists available overridable methods based on your initial keystrokes and inserts skeleton code for the method you select.

Bind a Key Combination to a Command without an Associated Menu Item
The first hurdle you are likely to consider when trying to expand beyond the above technique is: How do you use a key combination to initiate a particular command not tied to a menu item? For this you need to delve under the covers just a bit, adding a hook into the .NET framework. Specifically, you need to override one or more of the methods common to every Control listed in Table 2. ProcessCmdKey is the most appropriate for this purpose.

Type override in the code editor followed by a space. When you enter the space, Intellisense pops up with a list of all overridable methods. Select ProcessCommandKey?either by typing its initial characters or by scrolling in the Intellisense list, and then press Tab to auto-generate the complete overridden method (see Figure 12).

The stub method override that Visual Studio inserts simply calls the original base method. To make it do something different, you need to insert code before the single line of code provided. Assume, for this example, that you want Alt+M to perform some action not available on a menu. Here’s the altered method:

   protected override bool ProcessCmdKey(ref Message msg, Keys keyData)   {       if ((keyData & Keys.Alt) > 0 && (keyData & Keys.KeyCode) == Keys.M)       {           DoSomething();           return true; // indicate key has been handled       }       return base.ProcessCmdKey(ref msg, keyData);   }

This overridden method gets invoked for almost any keystroke a user might happen to type. Therefore, the gatekeeper code added in the example above ensures that both the Alt key and the M key are activated before calling the DoSomething() method and returning true. The return value is important: it signals that you have handled the key(s) and that no further propagation is necessary. If the user did not press Alt+M, the method calls the base method, which will return an appropriate Boolean value.

Modify Mouse Actions Based on Modifier Keys
Suppose your Form contains a Button for which you want to take one action in most cases, but a different action when users depress the Shift key while clicking the button. The Control.ModifierKeys is a static (class) property that acts as a convenient handle for you to examine the state of the Control, Shift, and Alt modifier keys at any point in your application. For example, you can check for the Shift key modifier during a mouse click with code similar to the following:

   private void loadButton_Click(object sender, EventArgs e)   {       if (Control.ModifierKeys == Keys.Shift)           DoAlternateAction();       else           DoPrimaryAction();

}

What if the user happens to press both Control and Shift? Because the previous code fragment checks for a single, specific modifier key, the above code would not invoke DoAlternateAction(). The Control.ModifierKeys property, though, is a bitwise combination of all modifier keys active at any given moment. To look for the Shift key irrespective of any other modifier keys, you would need to write the click handler like this:

   private void loadButton_Click(object sender, EventArgs e)      {       bool shiftPressed = (Control.ModifierKeys & Keys.Shift) > 0;       if (shiftPressed)           DoAlternateAction();       else           DoPrimaryAction();

}

The preceding code works because Keys.Shift (and similarly Keys.Alt and Keys.Control) are single bits. If this were not the case the simple logic shown would not suffice.

Similarly, if you want your alternate action to be invoked when a user presses a specific combination of modifier keys instead of just a single one, you can test multiple keys. For example, this fragment requires both the Control and Shift keys to be depressed before it will call DoAlternateAction(). It does not matter whether the Alt key is pressed:

   private void loadButton_Click(object sender, EventArgs e)   {       bool ctrlPressed = (Control.ModifierKeys & Keys.Control) > 0;       bool shiftPressed = (Control.ModifierKeys & Keys.Shift) > 0;       if (ctrlPressed && shiftPressed)           DoAlternateAction();       else           DoPrimaryAction();

}

Bind Multiple Key Combinations to a Single Menu Item
In the immediately preceding sections you learned:

  • How to bind shortcut keys to menu items.
  • How to bind shortcut keys without associated menu items.
  • How to work with multiple modifier keys.
?
Figure 13. Multiple Shortcuts on a Single Menu: (1) Create a separate menu item for each key shortcut. (2) Create a single shortcut and show only one of the possible shortcuts. (3) Create a single shortcut and show all shortcuts.

This section leverages all those techniques to enable you to bind multiple key combinations to a single menu item. Consider the scenario where you want to create a menu item binding Ctrl+F to some action. But you also want Ctrl+Shift+F to do the same action. (This tip applies whether the desired action is the same or not; it is just more poignant in this example to suppose it is the same.)

Approach 1: Create a menu item for each key combination.
Each menu item would indicate a different shortcut key. Both menu items would reference the same Click event handler. See Figure 13, item 1. You saw how to do this using the visual designer in the “Bind a Keystroke to a Menu Item Command” section. But because this approach is something users do not typically expect it would be rather confusing, to say the least!

Approach 2: Use a single menu item invoked on both an advertised key combination and a second, hidden key combination (Figure 13, item 2).
Having unadvertised or hidden key combinations, one could argue, is useful for expert users on occasion, or even for diagnostic purposes. But it is not very user-friendly? or is it? Say, for example that your application happens to attract the user to invoke this sequence often: Shift+G, Shift+T, Ctrl+F, Shift+X. Using the conventional approach, it would be awkward and unfriendly for the user to have to release the Shift key just before the Control+F then reapply it afterwards. By making Control-Shift-F do exactly what Control+F does, the user can continue to hold down the Shift key while invoking it, even though technically it is the “wrong” key combination.

To implement this, consider this problem as two subproblems. The first subproblem is identical to Approach 1 above, discussed in the section “Bind a Keystroke to a Menu Item Command.” So set up a menu item that specifies Ctrl+F as its shortcut key. The second subproblem is identical to that discussed in the section “Bind a Keystroke to a Command without an Associated Menu Item.” In other words, you would override ProcessCmdKey with this:

   protected override bool ProcessCmdKey(ref Message msg, Keys keyData)   {      if (  (keyData & Keys.Control) > 0         && (keyData & Keys.Shift) > 0         && (keyData & Keys.KeyCode) == Keys.F)      {         FindElement();         return true;      }      return base.ProcessCmdKey(ref msg, keyData);   }

Approach 3: Use a single menu item that displays multiple shortcut keys (Figure 13, item 3).
If you are adamantly opposed to unadvertised keys, you can display all keystrokes on a single menu item. To do that, create a menu item in the visual designer but do not assign a shortcut key. Instead, find the ShortcutKeyDisplayString property for the menu item and manually set it to the list of key combinations you wish to display?in other words, literally type in Ctrl+F, Ctrl+Shft+F. You need to override ProcessCmdKey much like the previous example, but here the logic is even simpler:

   protected override bool ProcessCmdKey(ref Message msg, Keys keyData)   {      // Identify Ctrl-F while ignoring the Shift key      if ((keyData & Keys.Control) > 0          && (keyData & Keys.KeyCode) == Keys.F)      {         FindElement();         return true;      }      return base.ProcessCmdKey(ref msg, keyData);   }      

Remember, the goal is to invoke the same action for two different key combinations. Thus, you do not need to test the Shift key state in the preceding code. To apply this technique to perform different actions with different modifiers, simply add the logic to test for those, just as described in the section “Modify Mouse Actions Based on Modifier Keys.”

Remapping Keystrokes of Child Controls
Generally speaking, when a particular control in your application has focus, and the user presses a key, that control is first to receive the key. If that control doesn’t handle it, the keystroke bubbles up the control hierarchy; next, its parent gets the keystroke, and so on, up to the parent form. The Form.KeyPreview property alters this path, letting the form see OnKeyDown, OnKeyPress, and OnKeyUp events before any child controls. Figure 14 shows two different key sequences when you press the A key: the first without KeyPreview and the second with KeyPreview. In both scenarios the inputTextBox control has the focus (as shown in the black header bar). Without KeyPreview, then, the text box receives the A and processes it: it is stored in the buffer and displayed, as you would expect. The text box consumes the keystroke?it never goes to any other control’s handler for those three events. Look for the OnKeyDown, OnKeyPress, and OnKeyUp key panels in the first sequence?all other key panels have been grayed out for clarity (a handy feature of Keystroke Sandbox: just clicking a key panel toggles its grayed out state).

However, when you set KeyPreview to true, a different set of actions occurs, diagrammed in the second key sequence. Again, look just at the OnKeyDown, OnKeyPress, and OnKeyUp events. In this sequence, the form sees the key first for each of the three events. This gives your application the vital control to handle keystrokes that override any actions assigned by subordinate controls.

For example, suppose you have an editor control called DuplicatingTextBox, where Ctrl+L pastes the contents of the clipboard into the buffer twice. You want to use this control in your new application for other reasons but you want to deactivate the functionality of Ctrl+L. To do that, turn on KeyPreview, check for that keystroke combination, ensure that the DuplicatingTextBox has the focus and then consume the key. That way, the DuplicatingTextBox will never see it.

?
Figure 14. Sandbox Output: The key A activates different methods based on whether the form’s KeyPreview property is enabled or not. Here the focus is on the TextBox.

When programming key-handling overrides you may feel at times that it is not deterministic. There are scenarios in where repeating a key combination results in different key sequences in Keystroke Sandbox. Figure 15 shows one example. Both rows show the same two key sequences as in Figure 14; however, in Figure 14 the focus was on a TextBox while in Figure 15 the focus is on a CheckBox. Naturally, you would expect the key panels for the TextBox to be omitted in the latter figure, but actually a new key panel appears?ProcessMnemonic?that did not appear in Figure 14. And it’s invoked by the TextBox! (You would see the same method invoked by the DataGridView if it were not disabled?even though the DataGridView was never in the picture at all.) This happens because when a TextBox has focus and you type an A the TextBox consumes it. But when a CheckBox has the focus it doesn’t consume the A; instead, the keystroke bubbles up to the Form level. The Form, in turn, checks whether any of its controls have a mnemonic (thus the ProcessMnemonic panel) that matches the keystroke and, if so, it performs the associated action. One common scenario where this occurs is when you have assigned mnemonics to Button controls, so users can press the buttons with the keyboard rather than with the mouse.

?
Figure 15. Sandbox Output: The key A with and without the form’s KeyPreview property enabled. This time, though, the focus is not on the TextBox.

So far, you’ve been following the sequence of OnKeyXyz handlers. But note that other methods are always invoked first by the subordinate control that has focus, even when KeyPreview is enabled. Look at the first two frames of both key sequences in Figure 14. Regardless of the KeyPreview setting, the application invokes the TextBox handler for ProcessCmdKey before the ProcessCmdKey handler of the top-level form. That is, KeyPreview gives the form an advance look at regular characters; navigational, mnenomics, shortcuts, etc. follow the same route regardless of this setting.

Controlling the Tab Key
When simple controls (Buttons, Labels, CheckBoxes, etc.) have the focus, pressing the Tab key advances the focus to the next control in either a default or an assigned sequence. You may want to alter this behavior. If you are using a RichTextBox as a custom text editor, for example, you want the user to be able to type a Tab character in the RichTextBox rather than advancing the focus. Similarly, in a DataGridView, you might want Tab to move focus from cell to cell within the DataGridView, rather than advance to the next control. Altering the behavior of the Tab key depends on the particular control of interest. A DataGridView is the easiest one to adjust: Just set its StandardTab property to false. Standard tab behavior (true) is to move to the next control, so setting it to false makes the Tab keystroke stay within the DataGridView, moving focus from cell to cell.

For most other controls you need to override ProcessDialogKey to achieve the same effect. The code shown below exposes the ControlTabInsertsTab property, which lets you decide at control instantiation how you wish the Tab key to behave.

Author’s Note: If you prefer, you could call this property StandardTab, to match the corresponding property in the DataGridView. I chose the name ControlTabInsertsTab because I have used it only with a customized RichTextBox, where this name makes more sense to me.

The code below provides two additional properties that are more elegant when working with a TextBox or similar control, allowing you to choose (via the ExpandTab property) whether the Tab key inserts a Tab character or the equivalent number of spaces. If the latter, you can decide (via the Spaces property) how many spaces to insert:

   // When set to true, Control-Tab stays within control.   // When set to false, Tab stays within control.    public bool ControlTabInsertsTab { get; set; }      // When set to true, inserts spaces; otherwise, a tab character.   public bool ExpandTab { get; set; }      // Set this to the number of spaces to insert when ExpandTab is true.   public string Spaces { get; set; }      protected override bool ProcessDialogKey(Keys keyData)   {       Keys keyCode = (keyData & Keys.KeyCode);          // operate only on TAB and CTRL-TAB; skip if other modifiers depressed       if (keyCode == Keys.Tab &&            ((keyData & Keys.Alt) == 0) && ((keyData & Keys.Shift) == 0))       {           if (!ControlTabInsertsTab) // invert the sense           {               if ((keyData & Keys.Control) > 0)               { keyData &= ~Keys.Control; }               else { keyData |= Keys.Control; }           }           else { /* already has correct sense */ }              // Now internally, Ctrl-Tab always means insert tab           if ((keyData & Keys.Control) > 0)           {               SelectedText = ExpandTab ? Spaces : "	";               return true;           }           // ... while unmodified tab continues through with standard behavior       }          return base.ProcessDialogKey(keyData);   }

One related method to be aware of is ProcessTabKey which lets you explicitly select the next control in the current container and give it focus, just as if you had pressed the Tab key.

Close and Cancel a Subform with Escape
There are three situations when you may want users to be able to close a form by pressing Escape?a long-standing convention:

  1. Standard .NET dialogs such as OpenFileDialog and SaveFileDialog: These are the simplest case: They automatically accept Escape as equivalent to pressing the Cancel button on the form.
  2. Custom subforms/dialogs with a Button that cancels the form: This is very straightforward: just set the CancelButton property of the Form to your cancel button. This binds the Escape key to the button’s Click handler, thereby performing whatever action you have defined for the button.
  3. Custom subforms without a Cancel button: This requires only a couple of lines of code, putting into practice what you have learned about key handling. Depending on your needs, you could have the Escape key either hide or close the subform:
   protected override bool ProcessCmdKey(ref Message msg, Keys keyData)   {       if (keyData == Keys.Escape) {           Hide(); // could use Close() instead of Hide() depending on your needs           return true;       }       return base.ProcessCmdKey(ref msg, keyData);   }

Closing a Subform with Enter
Analogous to cancelling a subform with the Escape key, you may want to accept or acknowledge settings by just pressing Enter or Return, like standard .NET dialogs do. To do this, set the AcceptButton property to any existing Button on your form. If you do not have a suitable button, use code similar to the preceding example, checking for Keys.Enter.

Acting On Other Single Keystrokes
Besides Enter and Escape, you may want to perform actions on other single keystrokes. You do this by setting mnemonics on the displayed text of any Control that has a UseMnemonic property. (Not all Controls do so you will not find this among the base Control properties.)

To activate a mnemonic you must do two things: include an ampersand in the Text property of the control preceding the mnemonic letter, and set UseMnemonic to true. As an example, in Keystroke Sandbox, the Text property of the Load Grid button, for example, is set to Load &Grid, so pressing G (when focus is set to something that does not consume the keystroke) is equivalent to clicking on the Load Grid button. (Note that pressing Alt+G is more forgiving, as it will work irrespective of where the focus is.)

Working with DataGridViews
Just when you think you have covered it all? it turns out that the DataGridView, due to its complexity, provides quite a few more methods that you may override, giving you a good deal of control over all aspects of its behavior (see Table 5). The table does not include methods that handle raw messages.

Table 5. Common Key-Handling Methods in a DataGridView: The DataGridView gives you many possible keystroke-handling methods to override.
Method Processes?
ProcessAKey The A key.
ProcessCmdKey A command key.
ProcessDataGridViewKey Keys used for navigating in the DataGridView.
ProcessDeleteKey The DELETE key.
ProcessDialogChar A dialog character.
ProcessDialogKey Keys such as Tab, Escape, Enter, and arrows,used to control dialog boxes.
ProcessDownKey The DOWN ARROW key.
ProcessEndKey The END key.
ProcessEnterKey The ENTER key.
ProcessEscapeKey The ESC key.
ProcessF2Key The F2 key.
ProcessHomeKey The HOME key.
ProcessInsertKey The INSERT key.
ProcessLeftKey The LEFT ARROW key.
ProcessMnemonic A mnemonic character.
ProcessNextKey The PAGE DOWN key.
ProcessPriorKey The PAGE UP key.
ProcessRightKey The RIGHT ARROW key.
ProcessSpaceKey The SPACEBAR.
ProcessTabKey The TAB key.
ProcessUpKey The UP ARROW key.
ProcessZeroKey The 0 key.

Sending Keystrokes Programmatically
Occasionally you may have a need to send keystrokes to your application programmatically, such as when automating GUI control tests. The .NET framework provides the SendKeys.Send method to do just that. You’ll find that the linked method documentation provides all the details you need for sending special keys, and includes a small sample as well, so there’s no need to repeat that here.

Assigning an Event Handler vs. Overriding an OnKeyXyz method?
If you add a Button to an application you are designing and you want that button to perform some action when you click it, you assign an event handler, or delegate, to the Click event of the Button:

   loadButton.Click += new System.EventHandler(loadButton_Click);

You use a similar approach to key events. If, for example, you want to process certain keys for a TextBox, you might use:

   customTextBox.KeyUp += new System.EventHandler(customTextBox_KeyUp);

Assigning a handler lets you customize your child TextBox behavior externally (from the application) rather than internally (from within the custom TextBox class itself). It also lets you create different behaviors for different instances of your custom TextBox. To create a custom TextBox that processes keystrokes the same way for every instance, wire up the KeyUp event internally. Do this either by attaching a delegate to the KeyUp event (like that shown above) or by overriding the OnKeyUp method (similar to the many overridden methods you have seen earlier). The whole purpose of the OnKeyXyz methods is that they allow derived classes to handle events without attaching delegates. The MSDN documentation, in fact, states that that is the preferred technique for handling the event in a derived class. See, for example, OnKeyDown. So internally, while you could either assign a delegate to KeyUp or override the OnKeyUp method, prefer the latter.

There are probably other keystroke-handling methods hidden in the MSDN documentation, but the ones you’ve seen in this article?and the downloadable code for the Keystroke Sandbox application?should cover most situations. If you do run across others, please let me know.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist