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
Connect with Us