Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Overriding Keydown in a User Control using ProcessKeyPreview

0.00/5 (No votes)
7 Feb 2008 2  
How to easily override key processing in a form or user control

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.

//----------------------------------------------
// Define the PeekMessage API call
//----------------------------------------------

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);

//----------------------------------------------
 
/// <summary> 
/// Trap any keypress before child controls get hold of them
/// </summary>
/// <param name="m">Windows message</param>
/// <returns>True if the keypress is handled</returns>
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);
        }
        //else
        //{
        //    TrappedKeyUp(e);
        //}
            
        // Remove any WM_CHAR type messages if supresskeypress is true.
        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))
        {
        }
    }
}

/// <summary>
/// This routine gets called if a keydown has been trapped 
/// before a child control can get it.
/// </summary>
/// <param name="e"></param>
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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here