Click here to Skip to main content
15,891,733 members
Articles / Desktop Programming / WPF

Simple Code for Adding Hotkeys to WPF

Rate me:
Please Sign up or sign in to vote.
4.00/5 (2 votes)
13 Nov 2018CPOL1 min read 5.8K   1   4
Simple code for adding hotkeys to WPF

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

Hotkeys 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:

C#
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:

C#
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

  • 13th November, 2018 - Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
SuggestionWhy not using Key Bindings? Pin
Citiga15-Nov-18 19:34
Citiga15-Nov-18 19:34 
GeneralRe: Why not using Key Bindings? Pin
nebosite11-Nov-19 8:26
nebosite11-Nov-19 8:26 
QuestionNot always work! Pin
mag1314-Nov-18 6:36
mag1314-Nov-18 6:36 
AnswerRe: Not always work! Pin
nebosite11-Nov-19 8:28
nebosite11-Nov-19 8:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.