Table of Contents
A couple of days before I wrote this article, a member asked how to control the Caps, Num, and Scroll lock keys. I knew there were several implementations around, so I searched with a view to providing the poster with a link, but I couldn't find one that was complete. Many just read the state of these keys, some also allowed you to set them, but I couldn't find any that monitored them so your application was updated accordingly. So, this article is the result. The
ToggleKeysController class in the code attached will monitor these keys and raise events when a change is detected, and allow you to get or set the state.
If you just want to see how to use the class in your application(s) and aren't interested in how it does its thing, click here.
This wasn't quite as simple as I first thought it would be! The first thing to do was to get the state of the keys. I knew the
Console class had a
CapsLock property so I figured it would probably be the answer. Unfortunately, it doesn't have a
The next step was to set the key states. The ones that are in the
Console class are read only, so now that class was definitely out of the question. I had a look at
SendKeys for this, but that doesn't allow you to permanently change the state as demonstrated here.
Finally, I wanted to be able to monitor the state of these keys system wide so the information I was providing was always up to date. I couldn't find a way to do this either using classes in the framework, so not a good start.
The answer to all these problems was a little bit of PInvoke into user32.dll (and kernel32.dll) which has all the native functions we need.
Getting the Key States
Getting the state of a key requires a call to the
GetKeyState function. This function simply takes a value that represents a key, and returns a value indicating its state. This example shows just how easy it is to use to get the state of the CapsLock key.
bool isCapsLockOn = (GetKeyState(VK_CAPITAL) & 1) == 1;
The function definition is:
static extern short GetKeyState(byte nVirtKey);
Note: For simplicity elsewhere, I have defined each key as a byte. Strictly speaking, the function above should be declared with an
Setting the Key States
After investigating several different approaches to this, I decided the easiest way to do this was to simulate a key up/down cycle using the
keybd_event function when the state needed changing. The following example shows how to call this function to simulate a keypress of the CapsLock key.
keybd_event(VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYDOWN, 0);
keybd_event(VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
... and the function definition:
static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);
Monitoring the Keys
This was the most challenging part of all of this. The only way I could make this work successfully was to use a global hook. Global hooks literally hook into windows to intercept key / mouse events. Before we can hook into the system, we need the handle of our process' main module. To do this, we get the current process, get the main module of that process, then pass the name of that to the
GetModuleHandle function. Once we have the handle, we can install the hook using the
SetWindowsHookEx function specifying the type of hook we need, in our case, keyboard. This sounds more complicated than it actually is! Here is the code, followed by the function definitions:
IntPtr hookHandle = IntPtr.Zero;
using (Process currentProcess = Process.GetCurrentProcess())
using (ProcessModule currentModule = currentProcess.MainModule)
hookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc,
static extern IntPtr GetModuleHandle(string lpModuleName);
static extern IntPtr SetWindowsHookEx(int idHook,
LowLevelKeyboardProc lpfn, IntPtr hMod, int dwThreadId);
SetWindowsHookEx function takes a delegate of type
LowLevelKeyboardProc as its second parameter. I have passed it
hookProc, which is an instance of that delegate. This will call its associated method whenever a key event occurs. The documentation states that if the first parameter of the called method is less than zero, we should return with no further processing, so all we need to do is check that for zero and make sure it's a KeyDown and then check the key to make sure it's one we're interested in.
LowLevelKeyboardProc hookProc = KeyHookCallback;
IntPtr KeyHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
int wParamValue = wParam.ToInt32();
if (nCode >= 0 && (wParamValue == WM_KEYDOWN || wParamValue == WM_KEYUP))
ToggleKey key = (ToggleKey)Marshal.ReadByte(lParam);
if (key == ToggleKey.CapsLock || key == ToggleKey.NumLock ||
key == ToggleKey.ScrollLock)
return CallNextHookEx(hookHandle, nCode, wParam, lParam);
delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
So now we know that one of our keys has been pressed. Unfortunately, it's not quite as simple as toggling a boolean field/property. Firstly, holding down a toggle button will call this method repeatedly, but the toggle state only changes the first time. The obvious answer is to read the state of the key instead, but if you try that, you will find it hasn't yet been updated and won't be until our thread's inactive, which is the second problem. In the first version, I launched a worker thread, then posted back to the original thread and checked the state. This was good for accuracy, but was probably overkill. In this version, I toggle a flag on keydown and reset the flag on keyup. This way, only the first keydown is considered valid. This presents a potential problem if the key in question is being held down during the initial synchronization of the states, as the first keydown received may not be a valid one. To deal with this, the first keyup after synchronization tests the state and corrects the value if needed.
if (wParamValue == WM_KEYUP)
keys[key].IsDown = false;
bool originalValue = keys[key].IsOn;
keys[key].IsOn = (GetKeyState(VK_CAPITAL) & 1) == 1;
if (keys[key].IsOn != originalValue)
ToggleKeyChangedEventArgs e =
new ToggleKeyChangedEventArgs(key, keys[key].IsOn);
keys[key].Confirmed = true;
else if (!keys[key].IsDown)
keys[key].IsDown = true;
keys[key].IsOn = !keys[key].IsOn;
ToggleKeyChangedEventArgs e =
new ToggleKeyChangedEventArgs(key, keys[key].IsOn);
Now, all that's left is to unhook when we're done. I've placed the hooking in the constructor, so to keep things clean, I've implemented the standard Dispose pattern and unhooked in
Dispose(bool disposing). Unhooking is simply a case of calling
UnhookWindowsHookEx passing the handle we got from the original
hookHandle = IntPtr.Zero;
static extern bool UnhookWindowsHookEx(IntPtr hhk);
The ToggleKeysController Class
The final class wraps all the above into a simple to use class with just three
public properties, three additional
public methods (plus
Dispose), and four events. All of these should be very logical and self explanatory, but all the code is documented and commented just in case! Here is the (public) class diagram.
The properties get or set the state of the key.
Stop install/uninstall the hook. These probably won't be needed as
Start is called by the constructor and
Stop is called in
Toggle simply toggles the state of the key you pass to it. Finally, the events are raised as keystates change, with the
ToggleKeyChanged event being raised for every toggle key state change.
The demo app uses the
ToggleKeysController class via a status strip. The state of the keys is indicated by the
ForeColor of the labels. For example, this code is called by the
CapsLockStateChanged event handler.
toolStripStatusLabelCAPS.ForeColor = OnForeColor;
toolStripStatusLabelCAPS.ForeColor = OffForeColor;
Clicking a label toggles the state of the respective button by calling the
void toolStripStatusLabelCAPS_Click(object sender, EventArgs e)
I haven't been able to verify this, but there are a few reports that apparently some antivirus software doesn't like programs that use global hooks as it thinks they are key loggers. I have tested this with the latest version of Norton AV available at the time of writing (under Vista and XP) and AVG Network Edition (under XP) with no problems. If your AV takes exception to it, then please let me know.
There's not much else to say; as now all the more complex stuff is wrapped in one class, using it is trivial. I hope you find some use for it!
- 30th August, 2009: Initial version.
- 3rd September, 2009: Version 2.