Reading Time: 4 minutes
This post is obsolete. Check out the new version.
When I first started coding QuickShift, I stumbled upon a blog post which described a Hotkey class the author had written based on the RegisterHotkey and UnregisterHotkey Windows API functions. I found it because someone had posted a link on this StackOverflow question which, as of this writing, is still the number two Google hit for “C# hotkey”. I really liked the way it was implemented and ended up using it with minimal modification as the parent class for my own hotkey implementation in QuickShift. But recently, as I considered how to rework QuickShift to make it more useful in Windows 7, I decided I need to move beyond hotkeys. So even though there are roughly one BILLION .NET-based keyboard hook implementations out there—in one form or another—I decided I wanted to write my own. It’s pretty obvious that I was heavily influenced by the Hotkey class referenced above. I can only hope that someone finds my KeyboardHook class as useful as I found that Hotkey class. But my debt to the Hotkey guy isn’t the only one I need to pay. Stephen Toub’s blog post conveniently laid out how to use all the necessary building blocks for a keyboard hook. And, even though I discovered this code project post by Emma Burrows after I was nearly finished with my own hook, I did borrow a couple of her ideas. Her class is actually very similar to what I produced. After I found it I almost regretted spending the time writing my own, but there are certain design choices I made that illustrate a slightly different agenda, making it worthwhile after all.
For one thing, I decided not to allow multiple hooks to have the same key combinations assigned to them. It didn’t seem logical. Instead, I specifically designed the KeyboardHook to handle multicast events. So if for whatever reason you wanted to assign more than one method to a hook’s Pressed event, that’s totally doable. Plus, all hook events execute asynchronously. That wasn’t something I intended to do from the outset, but I found that if the hook callback method didn’t return right away, Windows seemed to reassert its responsibility for the keystrokes. I discovered that during testing when I put a message box in the callback. It was supposed to display the box and then block further processing of the key strokes. I got the message box, but still got the keystroke output as well. (I was typing in notepad.) When I replaced the message box call with a
Console.WriteLine()it worked correctly, so my assumption is that Windows found the delay induced by a modal dialog box unacceptable. Calling the events asynchronously resolved that issue. [Edit: My assumption was correct. There is a default hook timeout somewhere in the neighborhood of 300ms.]
My KeyboardHook uses a modified version of the Windows.Forms.Keys enumeration called EnhancedKeys. Since the original enumeration doesn’t include a modifier for the Windows Logo key, I added it. Incidentally, I just noticed that WPF has its own keys enumeration that does include the Windows Logo modifier key. I guess I can add that to my growing list of reasons to learn WPF. One last thing worth stating: if you hate
out parameters, too bad! =P
Anyway, here’s the kitchen sink exposé of my hook class. You can download the code here. Feel free to use it, abuse it, distribute it, alter it, remove my
out parameter, whatever. Post your thoughts in the comments below.
KeyboardHook hook = new KeyboardHook("Override minimize all");
if (!hook.TrySetKeys(EnhancedKeys.WinLogo | EnhancedKeys.M, out errorMessage))
// If another hook already owns these keys, you will get an error here.
// Instead of minimizing all windows, WinLogo + M will now display
// this messagebox.
hook.Pressed += (s, e) =>
// Activate the hook.
// Sometime later...
// By default, the hook will only allow its Pressed event(s) to execute. To
// allow additional processing, set this property to true. In this example,
// setting AllowPassThrough to true will (1) display a message box and
// (2) minimize all windows.
hook.AllowPassThrough = true;
// By default, the hook's Pressed event(s) will only execute once per key press. To
// continuously execute these events while the key is held down, set this to true.
hook.AutoRepeat = true;
// Disengage to temporarily disconnect the hook from the system. To re-activate,
// call Engage() again.
// Kills off the hook. Don't forget to call Dispose or the hook's keys will remain
// unavailable until after its finalizer has executed. Dispose() calls Disengage() so
// it is not necessary to call both.
// Most of the rest is just informational
Console.WriteLine("Is alt key used: " + hook.Alt);
Console.WriteLine("Is shift key used: " + hook.Shift);
Console.WriteLine("Is win logo key used: " + hook.Windows);
Console.WriteLine("Is control key used: " + hook.Control);
Console.WriteLine("Current keys: " + hook.Keys);
Console.WriteLine("Current unmodified key: " + hook.UnmodifiedKey);
Console.WriteLine("Is hook active: " + hook.IsEngaged);
Console.WriteLine("Hook has no keys assigned: " + hook.IsEmpty);