Wednesday, August 11, 2010

Keyboard Events in Windows Forms

When the user presses a key on the keyboard, active WinForms controls raise multiple events. In the order they are raised, those events are PreviewKeyDown, KeyDown, KeyPress and KeyUp. Following is an explanation of when and how to use each one.

The first event raised by a control is the PreviewKeyDown event. As with all events raised by the Framework, the data for the event is provided by the e parameter; in this case, a PreviewKeyDownEventsArgs object. The purpose of this event is basically the IsInputKey property of that object.

The PreviewKeyDown event was added to the .NET Framework in version 2.0. Prior to that, the only way to specify whether a key is a regular input key or not was to derive your own custom control and override the IsInputKey method. With the addition of the PreviewKeyDown event, we can now handle it for a standard control and specify whether or not the key that was depressed is a regular input key. This way we can more easily provide one-off custom behaviour. For more information on treating a key as a regular input key, consult the MSDN documentation for the Control.IsInputKey method.

There are some similarities between the PreviewKeyDownEventArgs received by the PreviewKeyDown event handler and the KeyEventArgs received by the KeyDown and KeyUp event handlers, so it’s worth covering them altogether now. The way in which you determine the key, or key combination, that was depressed (PreviewKeyDown and KeyDown) or released (KeyUp) is the same for all three events.

The first common property of note is KeyCode. It returns a value from the Keys enumeration that corresponds to the key that was just depressed or released. The KeyValue property is similar, but it returns an Int32 containing the numeric value of the key. The Alt, Control and Shift properties are all Booleans and indicate whether the corresponding modifier key was down when the key contained in KeyCode was depressed or released. The Modifiers property provides the state of all three modifiers together in a bitwise combination of Keys.Alt, Keys.Control and Keys.Shift values, depending on which modifiers are down. The KeyData property is a bitwise combination of KeyCode and Modifiers.

The next event to be raised is the KeyDown event. Note that, if the key pressed is a special key and the PreviewKeyDown event handler does not set e.IsInputKey to True, the KeyDown event and all subsequent events will never be raised. When a control does raise the KeyDown event, by default, neither its parent control nor any other ancestor controls will raise a KeyDown event. The same is true of the KeyPress and KeyUp events. You can override this default behaviour by setting the KeyPreview property of the form containing the control to True. The form will now raise the corresponding event before the control does. In this case, we can set e.Handled to True in the form’s event handler to prevent the control from also raising the same event.

The purpose of the KeyDown event is to trap specific keys, or key combinations, as they are depressed. As mentioned earlier, the KeyDown event receives a KeyEventArgs object as an argument, which has KeyCode, KeyValue, Alt, Control, Shift, Modifiers and KeyData properties with which to determine which key or keys were depressed. By way of example, here are multiple ways that you could detect the Ctrl+W and Ctrl+Shift+W key combinations:

C#

// Determine whether the W key was just depressed.
if (e.KeyCode == Keys.W)
{
// Determine whether the desired modifier keys were down at the time.
if (e.Control && !e.Alt)
{
if (e.Shift)
{
// Ctrl+Shift+W
}
else
{
// Ctrl+W
}
}
}

// Determine whether the W key was just depressed.
if (e.KeyCode == Keys.W)
{
// Determine whether the desired modifier keys were down at the time.
switch (e.Modifiers)
{
case Keys.Control:
// Ctrl+W
break;
case Keys.Control | Keys.Shift:
// Ctrl+Shift+W
break;
}
}

// Determine whether the W key was just depressed while the desired modifier keys were down.
switch (e.KeyData)
{
case Keys.Control | Keys.W:
break;
// Ctrl+W
case Keys.Control | Keys.Shift | Keys.W:
break;
// Ctrl+Shift+W
}

VB

'Determine whether the W key was just depressed.
If e.KeyCode = Keys.W Then
'Determine whether the desired modifier keys were down at the time.
If e.Control AndAlso Not e.Alt Then
If e.Shift Then
'Ctrl+Shift+W
Else
'Ctrl+W
End If
End If
End If

'Determine whether the W key was just depressed.
If e.KeyCode = Keys.W Then
'Determine whether the desired modifier keys were down at the time.
Select Case e.Modifiers
Case Keys.Control
'Ctrl+W
Case Keys.Control Or Keys.Shift
'Ctrl+Shift+W
End Select
End If

'Determine whether the W key was just depressed while the desired modifier keys were down.
Select Case e.KeyData
Case Keys.Control Or Keys.W
'Ctrl+W
Case Keys.Control Or Keys.Shift Or Keys.W
'Ctrl+Shift+W
End Select

Hopefully this demonstrates that, generally speaking, when you’re interested in specific key combinations, using the KeyData property produces the most succinct code.

Another important note is how to detect the Ctrl, Shift and Alt keys. The keys are treated differently when you want to detect that the key itself was just depressed compared to when you want to detect that the key was already down when another key was depressed. The following code detects when each of the Ctrl, Shift and Alt keys themselves are depressed:

C#

switch (e.KeyCode)
{
case Keys.LControlKey:
// The left Ctrl key was just depressed.
break;
case Keys.RControlKey:
// The right Ctrl key was just depressed.
break;
case Keys.LShiftKey:
// The left Shift key was just depressed.
break;
case Keys.RShiftKey:
// The right Shift key was just depressed.
break;
case Keys.LMenu:
// The left Alt key was just depressed.
break;
case Keys.RMenu:
// The right Alt key was just depressed.
break;
}

switch (e.KeyCode)
{
case Keys.ControlKey:
// Either Ctrl key was just depressed.
break;
case Keys.ShiftKey:
// Either Shift key was just depressed.
break;
case Keys.Menu:
// Either Alt key was just depressed.
break;
}

VB

Select Case e.KeyCode
Case Keys.LControlKey
'The left Ctrl key was just depressed.
Case Keys.RControlKey
'The right Ctrl key was just depressed.
Case Keys.LShiftKey
'The left Shift key was just depressed.
Case Keys.RShiftKey
'The right Shift key was just depressed.
Case Keys.LMenu
'The left Alt key was just depressed.
Case Keys.RMenu
'The right Alt key was just depressed.
End Select

Select Case e.KeyCode
Case Keys.ControlKey
'Either Ctrl key was just depressed.
Case Keys.ShiftKey
'Either Shift key was just depressed.
Case Keys.Menu
'Either Alt key was just depressed.
End Select

Note that you can detect either that specifically the left or right key of the two was depressed, or just that either of them was. Contrast the code above with that below, which detects whether the Ctrl, Shift or Alt keys were already down when some other key was depressed:

C#

switch (e.Modifiers)
{
case Keys.Control:
// Ctrl
break;
case Keys.Shift:
// Shift
break;
case Keys.Alt:
// Alt
break;
case Keys.Control | Keys.Shift:
// Ctrl+Shift
break;
case Keys.Control | Keys.Alt:
// Ctrl+Alt
break;
case Keys.Shift | Keys.Alt:
// Shift+Alt
break;
case Keys.Control | Keys.Shift | Keys.Alt:
// Ctrl+Shift+Alt
break;
}

VB

Select Case e.Modifiers
Case Keys.Control
'Ctrl
Case Keys.Shift
'Shift
Case Keys.Alt
'Alt
Case Keys.Control Or Keys.Shift
'Ctrl+Shift
Case Keys.Control Or Keys.Alt
'Ctrl+Alt
Case Keys.Shift Or Keys.Alt
'Shift+Alt
Case Keys.Control Or Keys.Shift Or Keys.Alt
'Ctrl+Shift+Alt
End Select

Note the difference between the Keys values used to detect the keys themselves, i.e. ControlKey, ShiftKey and Menu, and the corresponding modifier keys, i.e. Control, Shift and Alt.

Now that we’ve seen the KeyDown event, let’s revisit the PreviewKeyDown event for a moment with an example that involves both. As mentioned earlier, when a special key is detected, no KeyDown event is raised. The example provided in the MSDN documentation for the Control.IsInputKey method shows how create a custom TextBox control that overrides the IsInputKey method and treats the Tab key as a regular input key. This allows the Tab key to raise a KeyDown event, upon which the control replaces the selected text with four spaces. Following is that example reworked using the PreviewKeyDown and KeyDown events of a standard TextBox, rather than the IsInputKey and OnKeyDown methods of a custom control.

C#

private void textBox1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{
if (e.KeyData == Keys.Tab)
{
e.IsInputKey = true;
}
}

private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyData == Keys.Tab)
{
this.textBox1.SelectedText = " ";
}
}

VB

Private Sub TextBox1_PreviewKeyDown(ByVal sender As Object, _
ByVal e As PreviewKeyDownEventArgs) Handles TextBox1.PreviewKeyDown
If e.KeyData = Keys.Tab Then
e.IsInputKey = True
End If
End Sub

Private Sub TextBox1_KeyDown(ByVal sender As Object, _
ByVal e As KeyEventArgs) Handles TextBox1.KeyDown
If e.KeyData = Keys.Tab Then
Me.TextBox1.SelectedText = " "
End If
End Sub

Normally, the Tab key would be consumed by the system, causing focus to shift to the next control and no KeyDown event to be raised. The code above detects the Tab key when it is depressed in the absence of modifier keys and specifies that it should be treated as a regular input key. As a result, focus does not shift and the KeyDown event is raised, when four spaces are entered into the TextBox in place of the currently selected text.

The other property of interest provided by the KeyEventArgs class is SupressKeyPress, which prevents keyboard input being passed on to the control. The following example uses the SuppressKeyPress property to ignore all upper case input.

C#

private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
if (e.Modifiers == Keys.Shift)
{
e.SuppressKeyPress = true;
}
}

VB

Private Sub TextBox1_KeyDown(ByVal sender As Object, _
ByVal e As KeyEventArgs) Handles TextBox1.KeyDown
If e.Modifiers = Keys.Shift Then
e.SuppressKeyPress = True
End If
End Sub

That brings us to the KeyPress event, which is raised after KeyDown and before KeyUp if and only if the key or key combination depressed should result in input being entered into the control. Unlike the other events mentioned here, which are about actual keys on the keyboard, KeyPress is about the text that those keys produce.

The KeyPress event handler receives a KeyPressEventArgs object. This object has a Handled property that behaves just like the KeyEventArgs.Handled property in the KeyDown and KeyUp event handlers. The only other property is KeyChar, which contains the Char that is actually entered into the control. Following is an example using the KeyPress event to ignore upper case input, as the previous example does using KeyDown.

C#

private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
if (char.IsUpper(e.KeyChar))
{
e.Handled = true;
}
}

VB

Private Sub TextBox1_KeyPress(ByVal sender As Object,
ByVal e As KeyPressEventArgs) Handles TextBox1.KeyPress
If Char.IsUpper(e.KeyChar) Then
e.Handled = True
End If
End Sub

It’s worth noting that the KeyChar property was read-only in early versions of .NET but, from .NET 2.0, was made read/write. This means that you can change the input character in the KeyPress event handler if desired. The following example converts all upper case input to lower case.

C#

private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
if (char.IsUpper(e.KeyChar))
{
e.KeyChar = char.ToLower(e.KeyChar);
}
}

VB

Private Sub TextBox1_KeyPress(ByVal sender As Object, _
ByVal e As KeyPressEventArgs) Handles TextBox1.KeyPress
If Char.IsUpper(e.KeyChar) Then
e.KeyChar = Char.ToLower(e.KeyChar)
End If
End Sub

The last event in the sequence is KeyUp, which is raised when a key is released. The KeyUp event handler also receives a KeyEventArgs object and, besides the fact that SuppressKeyPress isn't really of use, all properties behave as they do for KeyDown. Generally speaking, the KeyDown and KeyPress events get used more than KeyUp, but there are times when you want to perform an action when a key is released rather than when it’s depressed.

Finally, it’s worth noting how these events behave when a key is held down. When a key is first depressed, the PreviewKeyDown event is raised first, followed by the KeyDown event and then, if applicable, the KeyPress event. If the key is held down then this sequence will be repeated over and over. When the key is released, a single KeyUp event is raised.

Hopefully this information makes it clear how, when and why to make use the four events associated with .NET keyboard input.