Denis Gladkikh

outcoldman

My personal blog about software development

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.

<p>First off all we need to import WinAPI methods:</p>  
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