Introduction
Recently I was working on a very complex user control with lots of child controls on it and wanted to be able to override the handling of the keydown
event in a single place (the main user control).
I added the keydown event to my user control and noticed that it never got fired. This seemed to be because the child controls were handling them instead and not passing them to the main control.
On a form, you can set Form.KeyPreview
to True
which will allow the form to receive key events before they are passed to the control that has focus. Unfortunately, this is not available on user controls.
Some searching on the Internet revealed that the ProcessKeyPreview
event which when overridden in a user control will allow you to trap the keyboard messages before the child controls get them.
Unfortunately the ProcessKeyPreview
is not very friendly and passes you the Windows messages. This means you need to know the message number, handle repeating keys, handle control keys, etc.
I did a bit of reflecting on the framework and found that it's actually pretty easy to turn the messages into standard keydown
and keyup
events which makes it much easier to code.
I thought someone may find it useful.
Using the Code
To use the code, simply paste it into your control. Then you just need to decide whether you need Keydown
and/or keyup
events and implement them as you see fit.
private struct MSG
{
public IntPtr hwnd;
public int message;
public IntPtr wParam;
public IntPtr lParam;
public int time;
public int pt_x;
public int pt_y;
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool PeekMessage([In, Out] ref MSG msg,
HandleRef hwnd, int msgMin, int msgMax, int remove);
protected override bool ProcessKeyPreview(ref Message m)
{
const int WM_KEYDOWN = 0x100;
const int WM_KEYUP = 0x101;
const int WM_CHAR = 0x102;
const int WM_SYSCHAR = 0x106;
const int WM_SYSKEYDOWN = 0x104;
const int WM_SYSKEYUP = 0x105;
const int WM_IME_CHAR = 0x286;
KeyEventArgs e = null;
if ((m.Msg != WM_CHAR) && (m.Msg != WM_SYSCHAR) && (m.Msg != WM_IME_CHAR))
{
e = new KeyEventArgs(((Keys)((int)((long)m.WParam))) | ModifierKeys);
if ((m.Msg == WM_KEYDOWN) || (m.Msg == WM_SYSKEYDOWN))
{
TrappedKeyDown(e);
}
if (e.SuppressKeyPress)
{
this.RemovePendingMessages(WM_CHAR, WM_CHAR);
this.RemovePendingMessages(WM_SYSCHAR, WM_SYSCHAR);
this.RemovePendingMessages(WM_IME_CHAR, WM_IME_CHAR);
}
if (e.Handled)
{
return e.Handled;
}
}
return base.ProcessKeyPreview(ref m);
}
private void RemovePendingMessages(int msgMin, int msgMax)
{
if (!this.IsDisposed)
{
MSG msg = new MSG();
IntPtr handle = this.Handle;
while (PeekMessage(ref msg,
new HandleRef(this, handle), msgMin, msgMax, 1))
{
}
}
}
private void TrappedKeyDown(KeyEventArgs e)
{
if (e.KeyCode == Keys.A)
{
e.Handled = true;
e.SuppressKeyPress = true;
}
}
Points of Interest
Note that the ProcessKeyPreview
can return true
or false
to indicate whether the keypress has been handled. However, if you are not handling it you should defer to the base ProcessKeyPreview
method.
Note: ModifierKeys
is part of the base Control
class and returns a value indicating which of the modifier keys (SHIFT, CTRL, and ALT) is in a pressed state. You can set this in the KeyEventArgs
as per usual...
History
- 02-Jan-2008
- Added Processing to remove messages if
e.SuppressKeyPress
is set in the KeyDown
event
- Changed message numbers to constants to make it easier to read
- 07-Feb-2008
- Removed references to Form to avoid confusion and mentioned
KeyPreview