Click here to Skip to main content
Licence CPOL
First Posted 31 Dec 2007
Views 39,018
Bookmarked 31 times

Overriding Keydown in a User Control using ProcessKeyPreview

By | 7 Feb 2008 | Article
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Laughing.John

Software Developer (Senior)

United Kingdom United Kingdom

Member



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Layout  Per page   
  Refresh
GeneralKeyUp doesn't fire !!! PinmemberS.Reda23:45 1 Mar '11  
GeneralThe way of the Form - very short :) PinmemberMichael Ochmann3:02 28 Oct '10  
GeneralRe: The way of the Form - very short :) PinmemberAussie ALF1:27 8 Apr '11  
Generalit does help, Thanks! Pinmemberliwenjiede17:24 2 Aug '10  
GeneralThe easiest way PinmemberJinzoX7:51 6 Oct '08  
GeneralThe Ultimate Easiest Way. PinmemberJinzoX8:09 6 Oct '08  
GeneralThanks PinmemberIlíon4:52 8 Jan '08  
QuestionWhat about Form.KeyPreview = true? Pinmember leppie 13:50 2 Jan '08  
AnswerRe: What about Form.KeyPreview = true? PinmemberLaughing.John2:47 3 Jan '08  
GeneralRe: What about Form.KeyPreview = true? Pinmember leppie 4:01 3 Jan '08  
GeneralRe: What about Form.KeyPreview = true? PinmemberLaughing.John4:51 3 Jan '08  
Generalless complicated PinmemberMr.PoorEnglish13:57 1 Jan '08  
I'm not shure (usually I do VB), but isn't it less complicated this way?
using System.Diagnostics;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
 
namespace EulerRotation
{
   public partial class Form2 : Form
   {
      public Form2()
      {
         InitializeComponent();
      }
 
      enum MsgCodes
      {
         KeyDown = 0x100,
         KeyUp = 0x101,
         AltKeyDown = 0x104,
         AltKeyUp = 0x105
      }
 
      MsgCodes[] ExaminedKeyActions = (MsgCodes[])Enum.GetValues(typeof(MsgCodes));
 
      /// <summary>Trap any keypress before child controls get hold of them</summary>
      /// <returns>True if the keypress is handled</returns>
      protected override bool ProcessKeyPreview(ref Message m)
      {
         //for debugging only
         MsgCodes MsgCode = (MsgCodes)m.Msg;
         Keys Key=(Keys)((int)(m.WParam)) | Control.ModifierKeys;
         if (Array.IndexOf(ExaminedKeyActions, MsgCode) >= 0)
         {               
            Debug.WriteLine(string.Concat(MsgCode, " ", Key));
         }
 
         if ((m.Msg == 0x100) || (m.Msg == 0x104))
         {
            KeyEventArgs e = new KeyEventArgs(((Keys)((int)(m.WParam))) | Control.ModifierKeys);
            TrappedKeyDown(e);
            if (e.Handled)
            {
               Debug.WriteLine("Trapped");
               return true;
            }
         }
 
         return base.ProcessKeyPreview(ref m);
      }
 
      private void TrappedKeyDown(KeyEventArgs e)
      {
         if (e.KeyCode == Keys.A)
         {
            e.Handled = true;
         }
      }
   }
}
When I tried this with a RichtextBox on Form2, the "a" - key passed through to the rtb, although beeing trapped.
hmm.
GeneralRe: less complicated [modified] PinmemberLaughing.John3:41 2 Jan '08  
GeneralFine :o) PinmemberMr.PoorEnglish23:42 2 Jan '08  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web04 | 2.5.120529.1 | Last Updated 7 Feb 2008
Article Copyright 2007 by Laughing.John
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid