Introduction

I was working on an app that needed hotkey support and found out technically how to do it, but did not see any very clean solutions, so I wrote my own. This code provides a dead-simple way to attach/detach a snippet of code to a hotkey in a WPF app.

Background

Hotkey s are a relic from early versions of Windows, so we need to use interop functionality to get to it. All of this is abstracted in the HotKeyHelper class, however the main trick is to get the relic window handle ( hwnd ) of the main window of your WPF application. The hwnd is not available at construction time, so we need to hook an event that occurs at a point where the handle is known.

Using the Code

The hotkey code is implemented to be "fire and forget", so you can add the key without having to explicitly remove it, but that is available if needed. As I mentioned in the background, it is necessary to create the HotKeyHelper at a time when the window has a valid hwnd we can access. OnSourceInitialized is a good place to do this:

HotKeyHelper _hotKeys; int _throwConfettiKeyId; protected override void OnSourceInitialized(EventArgs e) base .OnSourceInitialized(e); _hotKeys = new HotKeyHelper( this ); // Assign Ctrl-Alt-C to our ThrowConfetti() method. _throwConfettiKeyId = _hotKeys.ListenForHotKey( Key.C, HotKeyModifiers.Alt | HotKeyModifiers.Control, () => { this .ThrowConfetti(); } // put any code you want here // Key removal is handled implicitly, but you can explicitly remove // a key like this void DoSomeStuffLater() _hotKeys.StopListeningForHotKey(_throwConfettiKeyId);

Here is the actual code for the helper class:

using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Forms; using System.Windows.Input; using System.Windows.Interop; namespace HotKeyTools /// < summary > /// Simpler way to expose key modifiers /// < /summary > [Flags] public enum HotKeyModifiers None = 0 , Alt = 1 , // MOD_ALT Control = 2 , // MOD_CONTROL Shift = 4 , // MOD_SHIFT WindowsKey = 8 , // MOD_WIN /// < summary > /// A helpful interface for abstracting this /// < /summary > public interface IHotKeyTool : IDisposable int ListenForHotKey(System.Windows.Input.Key key, HotKeyModifiers modifiers, Action keyAction); void StopListeningForHotKey( int id); // -------------------------------------------------------------------------- /// < summary > /// A nice generic class to register multiple hotkeys for your app /// < /summary > // -------------------------------------------------------------------------- public class HotKeyHelper : IHotKeyTool // Required interop declarations for working with hotkeys [DllImport( " user32" , SetLastError = true )] [return: MarshalAs(UnmanagedType.Bool)] protected static extern bool RegisterHotKey( IntPtr hwnd, int id, uint fsModifiers, uint vk); [DllImport( " user32" , SetLastError = true )] protected static extern int UnregisterHotKey( IntPtr hwnd, int id); protected const int WM_HOTKEY = 0x312 ; /// < summary > /// The unique ID to receive hotkey messages /// < /summary > int _idSeed; /// < summary > /// Handle to the window listening to hotkeys /// < /summary > private IntPtr _windowHandle; /// < summary > /// Remember what to do with the hot keys /// < /summary > Dictionary<int, Action> _hotKeyActions = new Dictionary<int, Action>(); // -------------------------------------------------------------------------- /// < summary > /// ctor /// < /summary > // -------------------------------------------------------------------------- public HotKeyHelper(Window handlerWindow) // Create a unique Id seed _idSeed = ( int )((DateTime.Now.Ticks % 0x60000000 ) + 0x10000000 ); // Set up the hook to listen for hot keys _windowHandle = new WindowInteropHelper(handlerWindow).Handle; if (_windowHandle == null ) throw new ApplicationException( " Cannot find window handle. Try calling this on or after OnSourceInitialized()" ); var source = HwndSource.FromHwnd(_windowHandle); source.AddHook(HwndHook); // -------------------------------------------------------------------------- /// < summary > /// Listen generally for hotkeys and route to the assigned action /// < /summary > // -------------------------------------------------------------------------- private IntPtr HwndHook( IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) if (msg == WM_HOTKEY) var hotkeyId = wParam.ToInt32(); if (_hotKeyActions.ContainsKey(hotkeyId)) _hotKeyActions[hotkeyId](); handled = true ; return IntPtr .Zero; // -------------------------------------------------------------------------- /// < summary > /// Assign a key to a specific action. Returns an id to allow you to stop /// listening to this key. /// < /summary > // -------------------------------------------------------------------------- public int ListenForHotKey (System.Windows.Input.Key key, HotKeyModifiers modifiers, Action doThis) var formsKey = (Keys)KeyInterop.VirtualKeyFromKey(key); var hotkeyId = _idSeed++; _hotKeyActions[hotkeyId] = doThis; RegisterHotKey(_windowHandle, hotkeyId, ( uint )modifiers, ( uint )formsKey); return hotkeyId; // -------------------------------------------------------------------------- /// < summary > /// Stop listening for hotkeys. /// hotkeyId The id returned from ListenForHotKey /// < /summary > // -------------------------------------------------------------------------- public void StopListeningForHotKey( int hotkeyId) UnregisterHotKey(_windowHandle, hotkeyId); // -------------------------------------------------------------------------- /// < summary > /// Dispose - automatically clean up the hotkey assignments /// < /summary > // -------------------------------------------------------------------------- public void Dispose() foreach ( var hotkeyId in _hotKeyActions.Keys) StopListeningForHotKey(hotkeyId);

History

  • 13 th November, 2018 - Initial version
  • nice Idea but there are already key bindings in WPF which results in less code. Or is there any other benefit in your solution?
    Take a look here for using key bindings in wpf:
    c# - Create Key binding in WPF [ ^ ]
    So you're not relying on those win32 calls.
    Sign in · View Thread Some keys are not working from VS2017 like F12, if change functionality to F5 work OK, I think is not the single key!
    magsoft

    Sign in · View Thread I think that in some cases different apps will try to register the same hotkey. Not much we can do about this other than try to find unique hot key combinations.
    Sign in · View Thread