Introduction
This article goes over how to set, respond to, and remove a global keyboard hook. I have chosen to keep the associated application VERY simple, so that you may focus on the topic at hand, and adapt it easily to your needs.
In this example, we will be creating an application that is really nothing more than a textbox on a form. When the user presses a key combination, the form will appear (if it’s not already visible). If they press this combination again, the form will be hidden. This will happen whether the application has focus or not, since the hot key is registered globally with the OS.
Background
The operating system needs to keep track of the various hot keys and their associated callback functions. In order to alert the system that you want your thread to receive any matching WM_HOTKEY messages posted to the system, you must register it using the unmanaged RegisterHotKey
function.
That call is telling the system how to reach your thread (via a handle), the specific hot key (ID) and what key combination must be pressed in order to invoke it.
The ID is a unique number that the programmer must provide, and is explained in more detail below.
The combination of the handle and ID uniquely identify your hot key. In Windows XP and earlier, if you send an ID and handle that is already in the system, the old value will be clobbered, however, in newer systems, the call will fail, returning zero. There are also system hot keys that cannot be written over (Ctrl-Alt-Delete for instance), and the attempt to register them will fail
When the OS gets the notification that a key has been pressed, it forwards a message to the queue for the registered handle. This message is sent directly to the top of the threads message queue, where it is picked up and handled by WndProc
.
Keyboard Driver->System Message Queue -> Thread Message Queue -> WndProc
A hotkey that has been registered will exist until either a call to UnregisterHotKey is made, or the user logs out.
Using the Code
For clarity, I have placed all of the native code and definitions in a static class called "NativeMethods
"
To begin, we need to use the unmanaged function RegisterHotKey
in order to, well, register the hot key
Here’s the P/invoke signature
DllImport("user32.dll", SetLastError = true)]
internal static extern bool RegisterHotKey(IntPtr hwnd, int id, int fsModifiers, int vk);
hwnd
- the handle to your form; the one that’s going to process the desired action.
id
- refers to the unique id of this hook. MSDN recommends using the GlobalAddAtom
function to obtain this value. We are going to pass in a (hopefully) unique string to be registered in the Global Atom Table. The string is stored in this table, and the function returns a 16 bit integer to use when referencing it. This will help prevent “stomping” on another hot keys ID if you accidentally use a preexisting value.
fsModifiers
-the modifier keys that are to pressed in conjunction with your hotkey IE ALT,CTL, SHIFT, etc. This is specified by a set of flags.
vk
- refers to the integer value of the key you want to register as the hotkey. We’ll use the managed “Keys” enumeration to set this. (see references below for link to table)
First, we want to get a unique ID for our hotkey by passing in a string to the Global Atom Table. There is a cool way to generate a unique string presented on the P/Invoke site (see references). This uses the thread ID and type.
string atomName = Thread.CurrentThread.ManagedThreadId.ToString("X8") + this.GetType().FullName;
When you add an entry to the Global Atom Table, a unique identifier is returned
short id = NativeMethods.GlobalAddAtom(atomName);
Ok, so now we have a unique value for our hot key’s ID. Now we can call our function to register it:
RegisterHotKey(Handle, HotkeyID, (int)ModifierKeycodes.MOD_ALT, (int)Keys.D);
In the above example, we are using an enumeration for specifying the modifier key (which, for our example is the Alt key).
The values in this enumeration have been tagged as Flags, so they can be OR’d to get different combinations.
With (int)Keys.D
We are specifying the "D" key as the primary hot key.
Okey doke, now we have the hot key registered to our application, so how do we respond to the key press?
Remember, we’ve registered our application to be notified when a hot key is pressed. This means that our program will be passed a WM_HOTKEY message into its WndProc loop.
We need to override some of the functionality of this functions function (I couldn’t resist). We’re really only interested in handling this particular message, and specifically our Alt-D hotkey.
protected override void WndProc(ref Message msg)
{
switch (msg.Msg)
{
case NativeMethods.WM_HOTKEY:
if ((short)msg.WParam == m_HotkeyID)
{
if (Visible)
{
Hide();
}
else
{
Show();
BringToFront();
}
}
break;
default:
base.WndProc(ref msg);
break;
}
}
WndProc is a big ole switch statement that loops to check for messages so it can respond. In our case, we have it set so that if our thread gets a WM_HOTKEY message, we’re going to verify that it’s our combo, and then do something with it…in this case show/hide our form.
Summary and References
In order for your application to be able to respond to a particular combination of keys presses, you must register it as a HotKey with the operating system. When the operating system gets a keypress notification, it check to see if it is registered as a HotKey, and if so, passes a message to the tread that registered it. What the thread does with it is (more or less) up to the programmer, but needs to be intercepted and processed by the overridden WndProc function
- http://msdn.microsoft.com/en-us/library/ms646309(v=VS.85).aspx
- http://www.pinvoke.net/default.aspx/user32/registerhotkey.html
- http://msdn.microsoft.com/en-us/library/ms649060(v=VS.85).aspx
- http://msdn.microsoft.com/en-us/library/dd375731(v=VS.85).aspx