[WPF] Declare global hotkeys in XAML with NHotkey

A common requirement for desktop applications is to handle system-wide hotkeys, in order to intercept keyboard shortcuts even when they don’t have focus. Unfortunately, there is no built-in feature in the .NET framework to do it.

Of course, this is not a new issue, and there are quite a few open-source libraries that address it (e.g. VirtualInput). Most of them rely on a global system hook, which allow them to intercept all keystrokes, even the ones you’re not interested in. I used some of those libraries before, but I’m not really happy with them:

  • they’re often tied to a specific UI framework (usually Windows Forms), which makes them a bit awkward to use in another UI framework (like WPF)
  • I don’t really like the approach of intercepting all keystrokes. It usually means that you end up with a big method with lots of if/else if to decide what to do based on which keys were pressed.

A better option, in my opinion, is to listen only to the keys you’re interested in, and declare what to do for each of those. The approach used in WPF for key bindings is quite elegant:

    <KeyBinding Gesture="Ctrl+Alt+Add" Command="{Binding IncrementCommand}" />
    <KeyBinding Gesture="Ctrl+Alt+Subtract" Command="{Binding DecrementCommand}" />

But of course, key bindings are not global, they require that your app has focus… What if we could change that?

NHotkey is a very simple hotkey library that enables global key bindings. All you have to do is set an attached property to true on your key bindings:

    <KeyBinding Gesture="Ctrl+Alt+Add" Command="{Binding IncrementCommand}"
                HotkeyManager.RegisterGlobalHotkey="True" />
    <KeyBinding Gesture="Ctrl+Alt+Subtract" Command="{Binding DecrementCommand}"
                HotkeyManager.RegisterGlobalHotkey="True" />

And that’s it; the commands defined in the key bindings will now be invoked even if your app doesn’t have focus!

You can also use NHotkey from code:

HotkeyManager.Current.AddOrReplace("Increment", Key.Add, ModifierKeys.Control | ModifierKeys.Alt, OnIncrement);
HotkeyManager.Current.AddOrReplace("Decrement", Key.Subtract, ModifierKeys.Control | ModifierKeys.Alt, OnDecrement);

The library takes advantage of the RegisterHotkey function. Because it also supports Windows Forms, it is split into 3 parts, so that you don’t need to reference the WinForms assembly from a WPF app or vice versa:

  • The core library, which handles the hotkey registration itself, independently of any specific UI framework. This library is not directly usable, but is used by the other two.
  • The WinForms-specific API, which uses the Keys enumeration from System.Windows.Forms
  • The WPF-specific API, which uses the Key and ModifierKeys enumerations from System.Windows.Input, and supports global key bindings in XAML.

If you install the library from Nuget, add either the NHotkey.Wpf or the NHotkey.WindowsForms package; the core package will be added automatically.

16 thoughts on “[WPF] Declare global hotkeys in XAML with NHotkey”

  1. This is exactly what I need right now but am getting error:

    System.Windows.Markup.XamlParseException was unhandled
    Message=’Set property ‘NHotkey.Wpf.HotkeyManager.RegisterGlobalHotkey’ threw an exception.’ Line number ’19’ and line position ’61’.

    Message=Hot key is already registered. (Exception from HRESULT: 0x80070581)

  2. The problem I posted about above is actually two problems.

    1) There is no Unregister method exposed which could be called when the application closes.

    2) The current design doesn’t return true or false on the Register function. Some hot keys might already be in use by other, unrelated applications and we need a way to know if registration fails.

  3. Hi HK1,

    There is a Remove method (declared in HotkeyManagerBase) that you can use to unregister a hotkey you have registered. Just pass the name you used for the registration.

    The current design does tell you if the registration fails, by throwing an exception; IMO this is better than returning true of false, because a boolean doesn’t tell you *why* the registration failed.

  4. The remove method doesn’t work, keyAlreadyRegistered exception still pops up! Note: for my application, multiple instances execution is a must…
    I use manual registration, like below:

    1. I tried also, using a guid string to make my key name unique everytime the application starts up… still no luck.

      HotkeyManager.Current.AddOrReplace(“xxx”+guid …)

      I guess you probably use the actual key combinations to register, not the name given.

    2. digging a little deeper, it seems you are using WIN32 at the core. Anyway, I changed to WPF native way to accomplish this task.

      RoutedCommand rc = new RoutedCommand();
      rc.InputGestures.Add(new KeyGesture(Key.F6, ModifierKeys.Alt));
      CommandBindings.Add(new CommandBinding(rc, myHandler));

      hope above helps someone!

      1. Yes, my solution is based on Win32; the RegisterHotkey function registers global hotkeys, so the key combination has to be unique. If you don’t need the hotkeys to be global, you don’t need my solution at all… just use the normal WPF input bindings.

  5. Hi Thomas,

    I’m glad I’ve discovered your blog. Many interesting posts, thank you, keep up the good work!

    I have a question to you. Could you please recommend some WPF blogs (preferably active)?

    Kind regards,

  6. Thx for your work. Very helpful !!

    Just a question. Is it possible to cancel HotKeys used by another application. example :

    If i am in Word and use Hotkey like ² , my application fired but in word the ² is wrotten.
    I would that the shorcut was “canceled” for other application. Is it possible ?

    Thx four your interest !

  7. when I add HotkeyManager.RegisterGlobalHotkey in UI it shows warning that HotkeyManager recognized in windows presentation framework

    1. Hi venkata,

      Did you install the NuGet package? There should be nothing else to do. HotkeyManager is mapped to the standard XML namespace for WPF, so you don’t even need to declare the namespace yourself.

Leave a Reply

Your email address will not be published. Required fields are marked *