Table of Contents
Overview
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.
Unanticipated Problems
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 ScrollLock
property.
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:
[DllImport("user32.dll")]
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 int
.
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:
[DllImport("user32.dll")]
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,
GetModuleHandle(currentModule.ModuleName), 0);
}
}
[DllImport("kernel32.dll")]
static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("user32.dll")]
static extern IntPtr SetWindowsHookEx(int idHook,
LowLevelKeyboardProc lpfn, IntPtr hMod, int dwThreadId);
The 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);
[DllImport("user32.dll")]
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.
switch (key)
{
case ToggleKey.CapsLock:
if (wParamValue == WM_KEYUP)
{
keys[key].IsDown = false;
if (!keys[key].Confirmed)
{
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);
OnCapsLockChanged(e);
OnToggleKeyChanged(e);
}
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);
OnCapsLockChanged(e);
OnToggleKeyChanged(e);
}
break;
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 SetWindowsHookEx
call.
UnhookWindowsHookEx(hookHandle);
hookHandle = IntPtr.Zero;
[DllImport("user32.dll")]
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. Start
and Stop
install/uninstall the hook. These probably won't be needed as Start
is called by the constructor and Stop
is called in Dispose
. 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
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.
if (toggleKeysController.CapsLockOn)
toolStripStatusLabelCAPS.ForeColor = OnForeColor;
else
toolStripStatusLabelCAPS.ForeColor = OffForeColor;
Clicking a label toggles the state of the respective button by calling the ToggleKey
method.
void toolStripStatusLabelCAPS_Click(object sender, EventArgs e)
{
toggleKeysController.ToggleKey(ToggleKeys.CapsLock);
}
Possible Limitation?
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.
Conclusion
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!
History
- 30th August, 2009: Initial version.
- 3rd September, 2009: Version 2.