Register hotkey in system for WPF application

Couple days ago I got a question about how to register hotkey in Windows for WPF application. I remembered that one year ago I was solving the same problem in WinForms application, I was registering hot keys for my application, it was Vista Keys Extender project. I knew that my project worked, so I suggested author of question to use code of my project to solve his problem. But as we learned later in WPF message handle mechanism different from WinForms. So I started to find solution for WPF application. I copied my old code from my old project and started to rewrite it step-by-step.

First off all we need to import WinAPI methods:

internal class HotKeyWinApi
{
    public const int WmHotKey = 0x0312;
 
    [DllImport("user32.dll", SetLastError = true)]
    public static extern bool RegisterHotKey(IntPtr hWnd, int id, ModifierKeys fsModifiers, Keys vk);
 
    [DllImport("user32.dll", SetLastError = true)]
    public static extern bool UnregisterHotKey(IntPtr hWnd, int id);
}

In Vista Keys Extender project I implemented own enum KeyModifiers, but in WPF I don’t need to do this, because it has System.Windows.Input.ModifierKeys, which equals to my own enum. Also in my old project I used System.Windows.Forms.Keys, but in WPF enum System.Windows.Input.Key different, it has other values. I didn’t want to set reference from my new WPF project to assembly System.Windows.Forms, because I need just one enum. So I copied this enum from assembly to my new WPF project. Ok, so I did all preparations and now I need to realize main class HotKey:

public sealed class HotKey : IDisposable
{
    public event Action<HotKey> HotKeyPressed;
 
    private readonly int _id;
    private bool _isKeyRegistered;
    readonly IntPtr _handle;
 
    public HotKey(ModifierKeys modifierKeys, Keys key, Window window)
        : this (modifierKeys, key, new WindowInteropHelper(window))
    {
        Contract.Requires(window != null);
    }
 
    public HotKey(ModifierKeys modifierKeys, Keys key, WindowInteropHelper window)
        : this(modifierKeys, key, window.Handle)
    {
        Contract.Requires(window != null);
    }
 
    public HotKey(ModifierKeys modifierKeys, Keys key, IntPtr windowHandle)
    {
        Contract.Requires(modifierKeys != ModifierKeys.None || key != Keys.None);
        Contract.Requires(windowHandle != IntPtr.Zero);
 
        Key = key;
        KeyModifier = modifierKeys;
        _id = GetHashCode();
        _handle = windowHandle;
        RegisterHotKey();
        ComponentDispatcher.ThreadPreprocessMessage += ThreadPreprocessMessageMethod;
    }
 
    ~HotKey()
    {
        Dispose();
    }
 
    public Keys Key { get; private set; }
 
    public ModifierKeys KeyModifier { get; private set; }
 
    public void RegisterHotKey()
    {
        if (Key == Keys.None)
            return;
        if (_isKeyRegistered)
            UnregisterHotKey();
        _isKeyRegistered = HotKeyWinApi.RegisterHotKey(_handle, _id, KeyModifier, Key);
        if (!_isKeyRegistered)
            throw new ApplicationException("Hotkey already in use");
    }
 
    public void UnregisterHotKey()
    {
        _isKeyRegistered = !HotKeyWinApi.UnregisterHotKey(_handle, _id);
    }
 
    public void Dispose()
    {
        ComponentDispatcher.ThreadPreprocessMessage -= ThreadPreprocessMessageMethod;
        UnregisterHotKey();
    }
 
    private void ThreadPreprocessMessageMethod(ref MSG msg, ref bool handled)
    {
        if (!handled)
        {
            if (msg.message == HotKeyWinApi.WmHotKey
                && (int)(msg.wParam) == _id)
            {
                OnHotKeyPressed();
                handled = true;
            }
        }
    }
 
    private void OnHotKeyPressed()
    {
        if (HotKeyPressed != null)
            HotKeyPressed(this);
    }
}

And small sample of using:

public partial class MainWindow : Window
{
    private HotKey _hotkey;
 
    public MainWindow()
    {
        InitializeComponent();
        Loaded += (s, e) =>
                      {
                          _hotkey = new HotKey(ModifierKeys.Windows | ModifierKeys.Alt, Keys.Left, this);
                          _hotkey.HotKeyPressed += (k) => Console.Beep();
                      };
    }
}

You can look and download source code of this sample from my public SVN repository at assembla.

Comments (22)

zerkms ( ) #
gravatar
You said that

"I didn’t want to set reference from my new WPF project to assembly System.Windows.Forms"

but where did you get "Keys" class from then (which is in "RegisterHotKey" and in "HotKey")?
Denis Gladkikh ( ) #
gravatar
Hi zerkms, I decompiled System.Windows.Forms library and put Keys enum to my project: Keys.
zerkms ( ) #
gravatar
@Denis Gladkikh: yep, seen that right after I'd left a comment. Thanks for the reply.
meze ( ) #
gravatar
I have a strange exception on the line ComponentDispatcher.ThreadPreprocessMessage -= ThreadPreprocessMessageMethod:

LocalDataStoreSlot storage has been freed.

Is there any way to fix it except commenting that piece out?
Denis Gladkikh ( ) #
gravatar
Hi, meze, try to use latest version of HotKey class
meze ( ) #
gravatar
Wow, you're awesome! The new version works fine. Thank you.
andy ( ) #
gravatar
Thank you!!!!!
Carolina ( ) #
gravatar
Hi Denis,

What about if the user can customize his hot-keys..how do I map a System.Windows.Input.Key to the Keys Class?
Denis Gladkikh ( ) #
gravatar
Carolina, you can use KeyInterop Class
Carolina ( ) #
gravatar
Thanks a lot! you saved my day! :)
Matthieu ( ) #
gravatar
Hi Denis,

What is the difference between this and the event "KeyUp" of "KeyDown"?

With your solution is the window receiving the event whether or not it got focus?
Muhammad Usman ( ) #
gravatar
Nice Code
Zakhar ( ) #
gravatar
What a retarded version control you using, I can't just download .zip archive? I need to install svn to get source?
Eric Ouellet ( ) #
gravatar
Tanks a lot, very helpfull !

I didn't know about ComponentDispatcher.ThreadPreprocessMessage and was looking for it for hours
Chibby ( ) #
gravatar
Yet again, another quality, instructive piece of programming you generously share with the public. I really appreciate your spirit of giving and sharing.

The SVN repository is extremely convenient for everyone, you in particular, as you dont have to keep repackaging the projects. Once you've done and tested a build, you know it works and make it available for sharing.

Really appreciate it.

Thanks, so much.

Kind Regards

Ron
Christian ( ) #
gravatar
Thanks a lot for this!

I was stuck trying to find a way to create global shortcuts, that would trigger an event in my application, regardless of which window had focus or even if the application was in the background. This does all of that in an elegant way, thanks!
CodesInChaos ( ) #
gravatar
You shouldn't use `GetHashCode()` as id, since that will lead to id collisions between unrelated hotkeys. Depending on the situation either a simple counter, or the result of `GlobalAddAtom` is the correct solution.
Denis Gladkikh ( ) #
gravatar
CodesInChaos, agree. I do not remember why I did this with GetHashCode...
El ( ) #
gravatar
GetHashCode() works well. Here's how I implemented it:

in HotKeyWinApi:

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]

public static extern short GlobalAddAtom(string atomName);

.... and then in constructor instead of line using GetHashCode:

_id =

HotKeyWinApi.GlobalAddAtom(modifierKeys.ToString() + key.ToString() + this.GetType().FullName);

... could also use a Guid for the parameter:

Guid.NewGuid().ToString()
KCT ( ) #
gravatar
Great work! Thanks a lot
Benjamin ( ) #
gravatar
Hi! I just found that useful piece of code. Thank you so much for it. I am stuck on a little problem. How would I go if I wanted to catch all possible combinations of ModifierKeys with Keys.PrintScreen?

If I go with

_hotkey = new HotKey(ModifierKeys.None | ModifierKeys.Alt | ModifierKeys.Control | ModifierKeys.Shift | ModifierKeys.Windows, Keys.PrintScreen, this);

it is only catched, wenn all ModifierKeys and PrintScreen are pressed.

I also failed with the approach to register multiple hotkeys with different ModifierKey definitions, like

_hotkey = new HotKey(ModifierKeys.Alt, Keys.PrintScreen, this);

...

_hotkey = new HotKey(ModifierKeys.None, Keys.PrintScreen, this);

This try failes at the second constructor with the error, that the hotkey is already defined.

Can anyone help?
Denis Gladkikh ( ) #
gravatar
Benjamin, if you want to catch all possible combinations - you will need to initialize for each of them separate HotKey instance with specific set of keys.

And you see this error, because indeed this hotkey is defined and is used by Windows system. You cannot override what is already defined.
Submit Comment
If you want to get notifications about new comments at this topic, please fill email text box and check proper item. If you want to place source code in comment body place it in tags [code]...[/code], you can set language like this [code cs]...[/code], where cs can be cs, html, xml, java, js, php, sql, cpp, css.

 

busy