Blog Post:

Capturing Keypress With Windows Message Filter

Recently I ran in to a situation where I had to capture keystrokes at the application level and launch an action based on the keystroke.  I tried using the ‘keyPress’ event for the main form, but it was hit or miss based on which control had the focus.  The form was very large with many controls, so I didn’t want to have to create multiple event handlers and link everything together.

Another option was to use a global keyboard hook.  This worked reasonably well, except for the fact that any key press while the application was running (even in the background) would be processed by the application.  If I minimized the application and opened an internet browser, for example, every key pressed while using the browser was also being processed by the application. I ended up finding a solution that was global to my application, but not global to the entire machine.

Windows base applications are event driven meaning that an application waits to be told what is going on in the windows environment.  Once a windows event occurs it is passed on to the windows applications using Windows Messages.  So every key pressed, or mouse button clicked is then relayed to the application using these messages.   An Application Message Filter can be applied to catch that message and process it before the application acts on it.  Here is my example of how to add an Application Message Filter.

First, create a new Visual Studio project.  I used a basic C# Windows Forms application.  Add a new class called ‘ApplicationKeyboardHook’ which inherits from the IMessageFilter class.


class ApplicationKeyboardHook : IMessageFilter

{

   public const int WM_KEYDOWN = 0x0100;

   public const int WM_KEYUP = 0x0101;

   public delegate void KeyboardEventHandler(object sender, KeyboardEventArgs e);

   public KeyboardEventHandler KeyboardEvent;

   public ApplicationKeyboardHook()

   {

   }

   public bool PreFilterMessage(ref Message m)

   {

      if (m.Msg == ApplicationKeyboardHook.WM_KEYDOWN)

      {

         Keys latestKey = (Keys)m.WParam.ToInt32();

         System.Diagnostics.Trace.WriteLine("Key Pressed: '"; + latestKey.ToString() + '''");

         if (KeyboardEvent != null)

         {

            KeyboardEvent(this, new KeyboardEventArgs(latestKey));

         }

      }

      return false;

   }

   [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]

   private static extern Int16 GetKeyState(Keys virtualKeyCode);

}

The above class has a function called ‘PreFilterMessage.’ This is where the message will be intercepted and acted on before it is passed on to the application. In the function, I check to see if the message is the ‘keydown’ event. If so, I grab the parameter that has the key that was pressed and raise the event to pass the key to the subscribing class. In this case, it’s the main form.

I also wrote a quick event argument class so I can pass exactly what I need and I can handle the event in the calling form without having to parse any data. The event argument class looks like this:


public class KeyboardEventArgs : EventArgs

{

   public Keys LastKey;

   public KeyboardEventArgs()

   {

   }

   public KeyboardEventArgs(Keys Key)

   {

      LastKey = Key;

   }

}

Once the keyboard hook class is set up, we can use it in our main form.  To use the class, simply create an instance of the keyboard hook in the form and subscribe to the KeyboardEvent. To designate the keyboard hook as a pre-message filter, you must add the keyboard hook instance to the application.


ApplicationKeyboardHook kbHook = new ApplicationKeyboardHook();

//subscribe to keyboard event

kbHook.KeyboardEvent += ProcessPressedKey;

Application.AddMessageFilter(kbHook);

Don’t forget to create the handler function ProcessPressedKey. For this example, I am just writing it out to the output window.


private void ProcessPressedKey(object sender, KeyboardEventArgs e)

{

   Debug.WriteLine("Key Pressed = '" + e.LastKey.ToString() + "'" + Environment.NewLine);

}

 

******Edit:

To determine case or special characters pressed you could simply check the shift key state while processing each non-shift key press. The KeyboardEventArg class can be modified to include a shift key flag.


public class KeyboardEventArgs : EventArgs

{

   public Keys LastKey;

   public bool bShiftKey = false;

   public KeyboardEventArgs()

   {

   }

   public KeyboardEventArgs(Keys Key)

   {

      LastKey = Key;

   }

   public KeyboardEventArgs(Keys Key, bool bShift)

   {

      LastKey = Key;

      bShiftKey = bShift;

   }

}

And to check the shift key…


public bool PreFilterMessage(ref Message m)

{

   if (m.Msg == ApplicationKeyboardHook.WM_KEYDOWN)

   {

      Keys latestKey = (Keys)m.WParam.ToInt32();

      System.Diagnostics.Trace.WriteLine("Key Pressed: '" + latestKey.ToString() + "'");

      Int16 keyState = GetKeyState(latestKey );

      bool bShift = (keyState & 0x8000) == 0 ? "false" : "true";

      if (KeyboardEvent != null)

      {

         KeyboardEvent(this, new KeyboardEventArgs(latestKey, bShift));

         return true;

      }

   }

   return false;

}

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]

private static extern Int16 GetKeyState(Keys virtualKeyCode);

Now that we have the event passed to us with the shift flag, we can process this in our handler in the form1 class. Special character recognition may require a lookup table of associated pairs, but the process would be the same.


private void ProcessPressedKey(object sender, KeyboardEventArgs e)

{

   //if looking for case

   string strLastKey = e.bShiftKey ? e.LastKey.ToString().ToUpper() : e.LastKey.ToString();

   //if looking for a special character

   strLastKey = e.bShiftKey ? GetSpecialCharacter(e.LastKey) : e.LastKey.ToString();

   Debug.WriteLine("Key Pressed = '" + e.LastKey.ToString() + "'" + Environment.NewLine);

}

I may do a follow up blog post with a clean and easy way to detect multiple simultaneous key pressed events such as “Ctrl+Alt+Del” or “Ctrl+F” if there is any interest.

3 Responses

  1. Useful post however has some issues when dealing with characters such as _ and – which are Keys.OemMinus and Keys.OemMinus (With Shift Pressed) as it has no idea of previous state. This also means it can’t deal with different cases of character due to the Keys enumeration.

    If you’ve any ideas on how to fix these issues I’d be interested 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

Get in Touch

If you have a product design that you would like to discuss, a technical problem in need of a solution, or if you just wish you could add more capabilities to your existing engineering team, please contact us.