Регистрация горячей клавиши (hotkey) в системе для WPF приложения
- 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.