Регистрация горячей клавиши (hotkey) в системе для WPF приложения

    • .NET
    • WinAPI
    • WPF
    • Hotkey
  • modified:
  • reading: 3 minutes

Пару дней назад мне попался на форумах GotDotNet вопрос [C# + WPF] Отлов нажатий клавиш, хуки, горячие клавиши в проекте WPF, зашел туда, посмотрел, и в голове всплыло, что год назад я реализовывал похожее на WinForms, регистрировал глобальные клавиши в системе для приложения. Делал я это для проекта Vista Keys Extender. Так как я на 100% помнил, что у меня там все работало, я сразу предложил автору вопроса посмотреть на тот мой проект, чтобы найти нужное решение. Однако, мой проект не подошел для решения данного вопроса, так как в WPF работа с сообщениями реализовано по-другому. Все же я решился помочь автору в этом вопросе и разобраться с данной проблемой. Отталкиваться я начал опять же с моего проекта, шаг за шагом разбираясь, что нужно изменить и как.

Итак, все начинается опять с импортирования WinAPI функций:

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);
}

В проекте Vista Keys Extender мне пришлось реализовать свой enum KeyModifiers, а в WPF есть реализованный enum System.Windows.Input.ModifierKeys, который полностью идентичен моему реализованному. А вот с System.Windows.Input.Key такой удачи не было, этот enum полностью отличается от enum, который я использовал в проекте Keys Extender – System.Windows.Forms.Keys от WinForms. Устанавливать зависимость на библиотеку System.Windows.Forms из-за одного enum мне не хотелось, потому я просто скопировал его полностью к себе в проект. Подготовительный этап окончен, осталось реализовать основной класс 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);
    }
}

И небольшой пример использования:

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();
                      };
    }
}

Посмотреть полностью и скачать исходный код этого примера можно с моего публичного SVN репозитория на assembla.

See Also