RSS Feed


WPF, low-level keyboard hook sample
The following demonstrates how to use a low-level hook in a .NET / WPF application. The KeyboardHook class below installs a keyboard hook by pinvoking SetWindowsHookEx. The hook callback method HookCallback is called by the system when a key is pressed.
public class KeyboardHook
{
    #region pinvoke details
    
    private enum HookType : int
    {
        WH_JOURNALRECORD = 0,
        WH_JOURNALPLAYBACK = 1,
        WH_KEYBOARD = 2,
        WH_GETMESSAGE = 3,
        WH_CALLWNDPROC = 4,
        WH_CBT = 5,
        WH_SYSMSGFILTER = 6,
        WH_MOUSE = 7,
        WH_HARDWARE = 8,
        WH_DEBUG = 9,
        WH_SHELL = 10,
        WH_FOREGROUNDIDLE = 11,
        WH_CALLWNDPROCRET = 12,
        WH_KEYBOARD_LL = 13,
        WH_MOUSE_LL = 14
    }

    public struct KBDLLHOOKSTRUCT
    {
        public UInt32 vkCode;
        public UInt32 scanCode;
        public UInt32 flags;
        public UInt32 time;
        public IntPtr extraInfo;
    }
    
    [DllImport("user32.dll")]
    private static extern IntPtr SetWindowsHookEx(
        HookType code, HookProc func, IntPtr instance, int threadID);

    [DllImport("user32.dll")]
    private static extern int UnhookWindowsHookEx(IntPtr hook);

    [DllImport("user32.dll")]
    private static extern int CallNextHookEx(
        IntPtr hook, int code, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam);

    #endregion

    HookType _hookType = HookType.WH_KEYBOARD_LL;
    IntPtr _hookHandle = IntPtr.Zero;
    HookProc _hookFunction = null;
    
    // hook method called by system
    private delegate int HookProc(int code, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam);

    // events
    public delegate void HookEventHandler(object sender, HookEventArgs e);
    public event HookEventHandler KeyDown;
    public event HookEventHandler KeyUp;

    public KeyboardHook()
    {
        _hookFunction = new HookProc(HookCallback);
        Install();
    }
    
    ~KeyboardHook()
    {
        Uninstall();
    }
    
    // hook function called by system
    private int HookCallback(int code, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam)
    {
        if (code < 0)
            return CallNextHookEx(_hookHandle, code, wParam, ref lParam);

        // KeyUp event
        if ((lParam.flags & 0x80) != 0 && this.KeyUp != null)
            this.KeyUp(thisnew HookEventArgs(lParam.vkCode));

        // KeyDown event
        if ((lParam.flags & 0x80) == 0 && this.KeyDown != null)
            this.KeyDown(thisnew HookEventArgs(lParam.vkCode));

        return CallNextHookEx(_hookHandle, code, wParam, ref lParam);
    }

    private void Install()
    {
        // make sure not already installed
        if (_hookHandle != IntPtr.Zero)
            return;
            
        // need instance handle to module to create a system-wide hook
        Module[] list = System.Reflection.Assembly.GetExecutingAssembly().GetModules();
        System.Diagnostics.Debug.Assert(list != null && list.Length > 0);

        // install system-wide hook
        _hookHandle = SetWindowsHookEx(_hookType, 
            _hookFunction, Marshal.GetHINSTANCE(list[0]), 0);
    }

    private void Uninstall()
    {
        if (_hookHandle != IntPtr.Zero)
        {
            // uninstall system-wide hook
            UnhookWindowsHookEx(_hookHandle);
            _hookHandle = IntPtr.Zero;
        }
    }
}

The callback method converts the low-level keyboard data into something more .NET friendly with the HookEventArgs class.
public class HookEventArgs : EventArgs
{
    // using Windows.Forms.Keys instead of Input.Key since the Forms.Keys maps
    // to the Win32 KBDLLHOOKSTRUCT virtual key member, where Input.Key does not
    public Keys Key;
    public bool Alt;
    public bool Control;
    public bool Shift;

    public HookEventArgs(UInt32 keyCode)
    {
        // detect what modifier keys are pressed, using 
        // Windows.Forms.Control.ModifierKeys instead of Keyboard.Modifiers
        // since Keyboard.Modifiers does not correctly get the state of the 
        // modifier keys when the application does not have focus
        this.Key = (Keys)keyCode;
        this.Alt = (System.Windows.Forms.Control.ModifierKeys & Keys.Alt) != 0;
        this.Control = (System.Windows.Forms.Control.ModifierKeys & Keys.Control) != 0;
        this.Shift = (System.Windows.Forms.Control.ModifierKeys & Keys.Shift) != 0;
    }
}

The main window creates an instance of the hook class and handles the KeyboardHook.KeyDown event.
// system-wide keyboard hook
private KeyboardHook _hook;

...

// install system-wide keyboard hook
_hook = new KeyboardHook();
_hook.KeyDown += new KeyboardHook.HookEventHandler(OnHookKeyDown);

// keyboard hook handler
void OnHookKeyDown(object sender, HookEventArgs e)
{
    ...
}

Some side topics in the sample are:

  • Toggle vista-glass in the client area on / off.
  • Toggle frame border on / off.
  • Move window by clicking in the client area.

image

Download sample code

 
Posted by Ralph Arvesen | 7 Comments | Trackback Url | Bookmark with:        
Tags:

Links to this Post

Comments

Sunday, 31 Aug 2008 08:50 by Logan
Hi Ralph--thanks a lot for the awesome post. However for some reason I can't get this working in my own applications, though your demo app works perfectly. Is there something I'm missing that needs to be done to use this? To test, all I did was create a new WPF Application project, override OnSourceInitialized(), and after the call to base, create an instance of KeyboardHook, and attach an event to it. However when the app runs and the window appears, no keyboard activity causes the event handler to run. Any thoughts?? I'm flabbergasted.

Sunday, 31 Aug 2008 08:59 by Logan
I've drilled down to the actual call to SetWindowsHookEx, which for some reason is returning 0. I tried capturing the Win32 error by means of SetLastError=true and GetLastWin32Error, but that returns 0 too. I cannot figure out the difference between my app and your demo!

Sunday, 31 Aug 2008 10:27 by Logan
Ah :) I think I've solved it: You need to disable the "Enable the Visual Studio hosting process" in the project properties pane, and then for some reason it magically works. I have no idea why this interferes, but at least it's finally working now, after several hours of process of elimination. :) Thanks again for the great code.

Thursday, 8 Jan 2009 05:22 by Ros
Hi Ralph! I got the same problem as Logan and I used his solvation. Any way thank you for real working KeyHOOK:)

Thursday, 5 Mar 2009 07:03 by Jesper
Hi, I included your code in an open source project I'm developing, I hope you don't mind. If you are curious the project can be found at http://toastify.codeplex.com/

Sunday, 31 May 2009 07:20 by Dave
How do you suppress some keystrokes after I have processed them - stop them going to other applications? Thanks in advance...

Tuesday, 14 Jul 2009 05:11 by Christian
One thing that I found is that this doesn't work with an application compiled for 64-bit. Probably because it's not User32 that's running for these. The simple solution was to compile for 32bit only. Any ideas how to get this to work with 64bit?

Name:
URL:
Email:
Comments:

CAPTCHA Image Validation