Keyboard event handling in .NET applications by Alfred Mirzagitov

By: Alfred Mirzagitov

Abstract: This article shows several different techniques for handling .NET keyboard events in an application

Whenever a new tool comes out, if it has something to do with building user interfaces, one of the very first questions asked is how to intercept and handle keyboard and mouse events. Experience also shows that the most of the confusion is caused by certain keys, such as the TAB, RETURN, ESCAPE, and ARROW keys rather than character keys since these are usually handled automatically by the OS or the framework.

This article will walk you through several steps of different complexity and show you how to intercept and handle keystrokes at the form level or at the application level. We will be mostly talking about keyboard keystrokes, although we shall mention mouse events. Mouse event handling is somewhat similar to keyboard events, but at the same time it probably could make a big article of its own.

Project 1: Using IsInputKey method

Let us start with the simplest case, by creating a blank WinForm with a text label component on it. To be able to intercept keystrokes, all we need to do is to add a new event handler for KeyDown event of the main form. This is a very easy task if you are using C#Builder. Although the programming steps are well described and easy to follow, we will assume that the reader has some familiarity with Borland C#Builder and WinForm based .NET applications. Since you are on the Borland site reading this article, you are probably using C#Builder, but the keyboard event handling mechanisms described here are not specific to Borland's C#Builder.

To add a new keyboard event handler we simply go to Object Inspector|Events and double click on the KeyDown property. C#Builder will create an empty event handler for you. Fill in the following code:

private void WinForm_KeyDown(object sender,
             System.Windows.Forms.KeyEventArgs e)
{
  e.Handled = true;

  switch(e.KeyCode) {
    case Keys.Left:   label1.Text = "Left Arrow";   break;
    case Keys.Right:  label1.Text = "Right Arrow";  break;
    case Keys.Up:     label1.Text = "Up Arrow";     break;
    case Keys.Down:   label1.Text = "Down Arrow";   break;
    case Keys.Space:  label1.Text = "Space";        break;
    default:          e.Handled   = true;           break;
  }
}

Compile and run the application. Press different keys, and see that the text label will show the detected key strokes when you press the arrow keys and the key. If a form has no visible or enabled controls that can have focus, it automatically receives all keyboard events.


We should probably say just a few words about keyboard events and System.Windows.Forms.KeyEventArgs here.

There are three key events which occur when the user presses and releases the key, in the following order:

KeyDown
KeyPress
KeyUp

The KeyPress event is not raised by noncharacter keys.

The KeyDown event occurs when the user presses any key. The KeyUp event occurs when the user releases the key. If the key is held down, there may be series of KeyDown events each time the key repeats. There is only one KeyUp event when the user releases the key.

KeyEventArgs parameter provides data for the currently pressed key to all three events.

In our case this parameter is passed to KeyDown event handler, and specifies the key the user pressed and whether any modifier keys (CTRL, ALT, and SHIFT) were pressed at the same time. In our sample we do not use any of the members of this class, but there are a few metods and properties that can be of interest:

-ToString: Returns a string that represents the current key (key name)
-Modifiers: Indicates which combination of modifier keys (CTRL,SHIFT,and ALT) were pressed
-Handled: Gets or sets a value indicating whether the event was handled
-KeyCode: Gets the key code for the event
-KeyData: Gets the key data for the event as Keys enumeration
-KeyValue: Gets the integer value for KeyData (includes key codes and modifiers)

Let us terminate the application, go back to design mode and drop a couple of buttons on the form. If you run the application now, you will notice that when you press the arrow keys the text label never changes. This is because by default the keystrokes are assigned to the control with the current focus, and since we have a couple of buttons on the form the keystrokes never get to the form KeyDown event handler. You will notice that the buttons react to the arrow and tab keys and the focus shifts from one button to another.

The buttons can have their own user defined KeyDown event handlers. It seems easy enough to add a KeyDown event handler to our buttons and point them to our existing event handler. We compile and run the application, and the behavior does not change. This is because certain keys, such as the TAB, RETURN, ESCAPE, and ARROW keys are handled by controls automatically, and our button KeyDown event handler never gets control when we press the arrow keys. When you press the Space key on your keyboard the text label will change to "Space," but this property has no effect on the arrow keys.

As we look through WinForm properties we discover a boolean property called KeyPreview, which seems to fit the bill. According to MS .NET help, this property indicates whether the form will receive key events before the event is passed to the control that has focus. We set KeyPreview to true, run the application again and learn that this statement is only partially true. When you run the application, you will notice that if you press the Space key on your keyboard the text label will change to "Space," but this property has no effect on the arrow keys.

So setting the main form's KeyPreview to true or pointing the buttons' KeyDown event handler to the form's KeyDown event handler are practically identical. In both cases the form KeyDown event handler gets the key, but arrow keys dont seem to surrender control to the event handler.

In order to have the arrow keys raise the KeyDown event, we must override the IsInputKey method in each focusable control on our form. The code for the override of the IsInputKey would need to determine if one of the special keys is pressed and return a value of true.

Leave KeyPreview property set to TRUE. Let us declare a new class, which derives from the standard button:

   public class MyButton : System.Windows.Forms.Button
   {
     protected override bool IsInputKey(Keys keyData)
     {
        bool ret = true;

        switch (keyData)
        {
          case Keys.Left:
            break;
          case Keys.Right:
            break;
          case Keys.Up:
            break;
          case Keys.Down:
            break;
          default:
            ret = base.IsInputKey(keyData);
            break;
        }
        return ret;
     }
   }

Place this code into the Project namespace just below the class WinForm. Now you may want to search for "System.Windows.Forms.Button" in WinForm scope and replace it with "MyButton":

    private System.Windows.Forms.Button button1;
    private System.Windows.Forms.Button button2;
will become:
    private MyButton button1;
    private MyButton button2;

Also
    this.button1 = new System.Windows.Forms.Button();
    this.button2 = new System.Windows.Forms.Button();
will become:
    this.button1 = new MyButton();
    this.button2 = new MyButton();

This code will handle the arrow keys for you. You will note that we set the Handled property of the second parameter to true. This is done to indicate that our callback handled the key stroke and we do not want the key stroke passed any further down the chain. If you leave the Handled property alone, the key will be handled more than once. For example, let us press the Space key. First, our form KeyDown event handler will handle it and the the label will change to "Space." Next the space key will be handled by the button, and you will notice how the button gets pressed down. This may not be the exact action you expected, and you may change the behavior by setting e.Handled to true.

The complete project is saved as "keybd1," and available for download as part of the code attached to this article.

Project 2: Using Message Filters

So far this all sounds awfully good... if we use only buttons in our project. But what if the form has dozens of different controls which can have focus, as it often happens in real-life applications? Overwriting all controls seem to be such a hassle. Don't panic, there is yet another way of handling keystrokes. This is done by implementing a IMessageFilter interface.

Application.AddMessageFilter() method allows us to add our own message filter to monitor Windows messages as they are dispatched to their destinations. To handle a message we need to create a class which implements IMessageFilter interface and overrides the PreFilterMessage() method with the code to handle the message. The method must return false, if the message is handled.

MS .NET help cautions us that adding message filters to the message pump for an application can degrade performance. However sometimes this is the only good way of handling messages. The nice part is that one filter can handle keyboard keystrokes and mouse events as we are about to demonstrate.

Again, let us start with an empty project with a label. Drop a couple of buttons on the form as well. Now let us open up the code window and add some code to the bottom of our project namespace:

namespace Project1
{
  . . .
  public class WinForm : System.Windows.Forms.Form
  {
  . . .
  }

  // This is a slightly modified MS sample code from .NET help
  // (nice job MS for using hardcoded constants!)
  //
  // Create message filter
  public class TestMessageFilter: IMessageFilter {
    private WinForm FOwner;
    public TestMessageFilter(WinForm aOwner)
    {
      FOwner = aOwner;
    }

    public bool PreFilterMessage(ref Message m) {
      // Blocks all the messages relating to the left mouse button.
      if (m.Msg >= 513 && m.Msg <= 515) {
        FOwner.label1.Text = "Processing the messages : " + m.Msg;
        return true;
      }
      return false;
    }
  }
}

If you simply compile this, you will get an error message saying that label1 is unknown. Also, we have not initiated our TestMessageFilter. We need to modify the WinForm code:

1. Find the declaration for "label1" and change it from "private" to "public"

2. Find the following piece of code:

        public WinForm()
	{
          InitializeComponent();
          //
	  // TODO: Add any constructor code after InitializeComponent call
	  //
        }
and change it to:
        public WinForm()
	{
          InitializeComponent();
	  Application.AddMessageFilter(new TestMessageFilter(this));
        }

Run the project and see how it reacts to LEFT mouse button click.

Adding Keyboard events is also very simple. We need to modify the message filter:

  // our message filter.
  public class TestMessageFilter: IMessageFilter
  {
    private WinForm FOwner;
    const int WM_KEYDOWN       = 0x100;
    const int WM_KEYUP         = 0x101;
    const int WM_LEFTMOUSEDOWN = 0x201;
    const int WM_LEFTMOUSEUP   = 0x202;
    const int WM_LEFTMOUSEDBL  = 0x203;

    public TestMessageFilter(WinForm aOwner)
    {
      FOwner = aOwner;
    }

    public bool PreFilterMessage(ref Message m)
    {
      if (m.Msg == WM_KEYDOWN)
	return FOwner.HandleKeys((Keys)(int)m.WParam & Keys.KeyCode);
      else
	return false;
    }
  }

and add code to WinForm to handle the keystrokes:

        public bool HandleKeys(Keys keyCode)
        {
          bool ret = true;

          switch(keyCode) {
            case Keys.Left:   label1.Text = "Left Arrow";   break;
            case Keys.Right:  label1.Text = "Right Arrow";  break;
            case Keys.Up:     label1.Text = "Up Arrow";     break;
            case Keys.Down:   label1.Text = "Down Arrow";   break;
            case Keys.Space:  label1.Text = "Space";        break;
            default:          ret         = false;          break;
          }
          return ret;
        }

Compile and run the application. This code can be found in the project called "keybd2." See the attached files.

Project 3: Using Message Filters in your main form

The final step will be to eliminate the special class we just created. As it turns out we can get by without using a separate class, if we implement IMessageFilter interface as part of our WinForm main form. The attached project called "keybd3" shows you how this is done.

All we need to do was to add ", IMessageFilter" to WinForm declaration, and move the slightly modified PreFilterMessage() into WinForm:

  public class WinForm : System.Windows.Forms.Form, IMessageFilter
  {
    . . .

    public bool PreFilterMessage(ref Message m)
    {
      if (m.Msg == WM_KEYDOWN)
        return HandleKeys((Keys)(int)m.WParam & Keys.KeyCode);
      else
	return false;
    }

Modify the WinForm() constructor to:

    public WinForm()
    {
      InitializeComponent();
      Application.AddMessageFilter(this);
    }

And finally, move the constants to WinForm and remove the declaration for TestMessageFilter. That's all, folks!

Source code related to this article can be downloaded from Borland Community web site. Click here for CodeCentral

Alfred Mirzagitov is a Senior Programmer for Software Science Inc, located in San Rafael, CA. You can contact him by email at: alfred@softsci.com


Server Response from: SC2

 
Copyright© 1994 - 2008 Embarcadero Technologies, Inc. All rights reserved. Contact Us   Site Map   Legal Notices   Privacy Policy   Report Software Piracy