My blog about application development on the .NET platform.

Implementing global hot keys in WPF

If you want your WPF application to be able to detect and handle key presses even when it is not currently activated or focused on the screen you could implement what is known as global hot keys in Windows.

A global, or system-wide, hot key is a key or a combination of keys that is associated with a particular window, which will receive messages whenever the user presses this key or key combination from anywhere in the system.

P/Invoke

There are no .NET API:s to register hot keys for your application but there is a native RegisterHotKey method that you can call from managed code using the Platform Invocation Services (or P/Invoke). P/Invoke is probably the easiest way to call unmanaged code (C++ in this case) from managed (.NET) code as you simply provide the C# compiler with a declaration of the unmanaged function and call it like you would call any other managed method.

The following two methods can be added to a C# class to be able to register and unregister a global hot key:

[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);

[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

The DllImport attribute specifies the name of the DLL that contains the method and the extern keyword tells the C# compiler that the method is implemented externally and that it won’t find any implementation or method body for it when compiling the application.

RegisterHotKey

The first argument to be passed to the RegisterHotKey function is a handle to the window in which you want to receive the “messages” that are generated by the operating system when a hot key is pressed. You can get handle for a WPF window by creating an instance of the managed System.Windows.Interop.WindowInteropHelper class and access its Handle property in an event handler for the window’s SourceInitialized event or by overriding the window’s OnSourceInitialized method. This event handler/method will be invoked once the handle has been completely created. Refer to the sample code below for details.

The second argument is an identifier and the third and fourth arguments specify the key or keys that must be pressed in combination with the hot key and the virtual key code of the hot key respectively.

The below sample implementation of the OnSourceInitialized method of a WPF window registers a hot key for the CTRL + CAPS LOCK key combination:

private const int HOTKEY_ID = 9000;

//Modifiers:
private const uint MOD_NONE = 0x0000; //[NONE]
private const uint MOD_ALT = 0x0001; //ALT
private const uint MOD_CONTROL = 0x0002; //CTRL
private const uint MOD_SHIFT = 0x0004; //SHIFT
private const uint MOD_WIN = 0x0008; //WINDOWS
//CAPS LOCK:
private const uint VK_CAPITAL = 0x14;

private HwndSource source;

protected override void OnSourceInitialized(EventArgs e)
{
    base.OnSourceInitialized(e);

    IntPtr handle = new WindowInteropHelper(this).Handle;
    source = HwndSource.FromHwnd(handle);
    source.AddHook(HwndHook);

    RegisterHotKey(handle, HOTKEY_ID, MOD_CONTROL, VK_CAPITAL); //CTRL + CAPS_LOCK
}

HwndHook is the event handler that will receive all window messages. In this event handler you can filter the messages based on the message id which is specified by the second parameter. The message’s wParam value can then be used to determine whether a hot key was pressed and you can handle the actual key press by extracting the key code for the pressed key as below:

private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    const int WM_HOTKEY = 0x0312;
    switch (msg)
    {
        case WM_HOTKEY:
        switch (wParam.ToInt32())
        {
            case HOTKEY_ID:
                int vkey = (((int)lParam >> 16) & 0xFFFF);
                if (vkey == VK_CAPITAL)
                {
                    //handle global hot key here...
                }
                handled = true;
                break;
        }
        break;
    }
    return IntPtr.Zero;
}

Return Value

Note that the RegisterHotKey function returns a Boolean value. This value indicates whether the registration of the global hot key was successful. The function will return false if the keystrokes specified for the hot key have already been registered by another application. So if you for example start two instances of an application that both try to register the same hot key, only one of them will be able to actually register and handle this hot key.

Other Applications

Also note that if the registration of the hot key in your application actually succeeds, other applications may not be able to
handle the key press of this key as you would expect.

As an example, you can run register the above hot key in a WPF application and run it. Then you open Notepad while the WPF application is still running and try to use the CAPS LOCK key (or CTRL + CAPS LOCK if you use the MOD_CONTROL modifier like in the sample code above) to capitalize the letters that you type into Notepad. You will then notice that you can no longer use the CAPS LOCK key to switch between uppercase or lowercase letters. This is because your application now handles the (CTRL +) CAPS LOCK key stroke. If you only want to be able to detect but not actually handle or prevent another application from handling the same key press you better implement a low-level keyboard hook but that’s another story that I save for another post.

Sample Code

Below is the complete sample code for a WPF window that handles a global hot key. Once CTRL + CAPS LOCK is pressed from anywhere in the system, a “CapsLock was pressed” message will be written to a TextBlock in the window. You could create a new WPF application project in Visual Studio and just copy the below code into the MainWindow.xaml.cs and MainWindow.xaml, compile and run the application and then try to press CTRL + CAPS LOCK on the keyboard from any other application. You should then see the Text property of the TextBlock getting updated for each such keystroke.

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

namespace Mm.Wpf.GlobalHotKeys
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        [DllImport("user32.dll")]
        private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);

        [DllImport("user32.dll")]
        private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

        private const int HOTKEY_ID = 9000;

        //Modifiers:
        private const uint MOD_NONE = 0x0000; //(none)
        private const uint MOD_ALT = 0x0001; //ALT
        private const uint MOD_CONTROL = 0x0002; //CTRL
        private const uint MOD_SHIFT = 0x0004; //SHIFT
        private const uint MOD_WIN = 0x0008; //WINDOWS
        //CAPS LOCK:
        private const uint VK_CAPITAL = 0x14;

        public MainWindow()
        {
            InitializeComponent();
        }

        private IntPtr _windowHandle;
        private HwndSource _source;
        protected override void OnSourceInitialized(EventArgs e)
        {
            base.OnSourceInitialized(e);

            _windowHandle = new WindowInteropHelper(this).Handle;
            _source = HwndSource.FromHwnd(_windowHandle);
            _source.AddHook(HwndHook);

            RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL, VK_CAPITAL); //CTRL + CAPS_LOCK
        }

        private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            const int WM_HOTKEY = 0x0312;
            switch (msg)
            {
                case WM_HOTKEY:
                    switch (wParam.ToInt32())
                    {
                        case HOTKEY_ID:
                            int vkey = (((int)lParam >> 16) & 0xFFFF);
                            if (vkey == VK_CAPITAL)
                            {
                                tblock.Text += "CapsLock was pressed" + Environment.NewLine;
                            }
                            handled = true;
                            break;
                    }
                    break;
            }
            return IntPtr.Zero;
        }

        protected override void OnClosed(EventArgs e)
        {
            _source.RemoveHook(HwndHook);
            UnregisterHotKey(_windowHandle, HOTKEY_ID);
            base.OnClosed(e);
        }
    }
}
<Window x:Class="Mm.Wpf.GlobalHotKeys.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBlock x:Name="tblock"></TextBlock>
    </Grid>
</Window>

If you want to use some other key than CAPS LOCK as a hotkey you simply replace the key code specified by the VK_CAPITAL constant that is passed to the RegisterHotKey method in the sample code above with another costant. You will find a complete list of valid virtual key codes listed on MSDN here.

See Also

RegisterHotKey function: http://msdn.microsoft.com/en-us/library/windows/desktop/ms646309(v=vs.85).aspx
HwndSourceHook Delegate: http://msdn.microsoft.com/en-us/library/system.windows.interop.hwndsourcehook(v=vs.110).aspx
UnregisterHotKey function: http://msdn.microsoft.com/en-us/library/windows/desktop/ms646327(v=vs.85).aspx

Advertisements

4 Comments on “Implementing global hot keys in WPF”

  1. Johan Bjurstam says:

    Hi Magnus.

    I’ve been using this code in an application for a couple of months and it’s been working great. Thank you so much! Today, however. I figured it was time to make the hotkeys configurable for the user, so I did some encapsulation.

    https://gitlab.com/jbjurstam/GlobalHotkeysWPF

    Hopefully this can be of use for other people as well.

    Let me know if you have any comments!

    Cheers

  2. Kay Lee says:

    Hello, Magnus.

    It’s working fine. My deepest appreciation to you ! Thank you !

  3. Raphael C. says:

    This is very good, but it won’t work with mouse buttons. Any ideas why?

  4. Tomas says:

    two thumps up. thank you


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s