📜 ⬆️ ⬇️

Registering global hotkeys using WPF

Perhaps you once really wanted to have in your application the ability to control anything through the global keys. And maybe you like programming with WPF technology. Then this topic is for you.


To solve the problem, it is worth understanding how the hot key mechanism works in Windows, since WPF methods that work with them directly do not exist. Therefore, we need to contact WinAPI.
We will need the functions below.

Hotkey registration:
BOOL WINAPI RegisterHotKey( __in_opt HWND hWnd, __in int id, __in UINT fsModifiers, __in UINT vk ); 

')
Hotkey removal:
 BOOL WINAPI UnregisterHotKey( __in_opt HWND hWnd, __in int id ); 


Registering a unique string for hotkey identification and getting its identifier (atom):
 ATOM GlobalAddAtom( LPCTSTR lpString ); 


And, accordingly, the removal of an atom:
 ATOM WINAPI GlobalDeleteAtom( __in ATOM nAtom ); 


Actually, the mechanism itself is quite simple - we register the hotkey identification string and with the help of the obtained atom we register the hot key itself. When you exit the application, we delete the registration of the hotkey and the atom — everything. As you can see, very simple. We now turn to the implementation.

In C #, we export these functions from the corresponding dlls:
 [DllImport("User32.dll")] public static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk); [DllImport("User32.dll")] public static extern bool UnregisterHotKey(IntPtr hWnd, int id); [DllImport("kernel32.dll")] public static extern Int16 GlobalAddAtom(string name); [DllImport("kernel32.dll")] public static extern Int16 GlobalDeleteAtom(Int16 nAtom); 


Then there is one small problem - handling calls WndProc. The fact is that in WPF, unlike Windows Forms, you cannot simply overload this function in the application window. But you can still handle calls WndProc as follows:
 public HotkeysRegistrator(Window window) { _windowHandle = new WindowInteropHelper(window).Handle; HwndSource source = HwndSource.FromHwnd(_windowHandle); source.AddHook(WndProc); } private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled) { if (msg == 0x0312) { //   } return IntPtr.Zero; } 


We are fully prepared to register hotkeys. Add to our class, for example, this method:
 private Dictionary<Int16, Action> _globalActions = new Dictionary<short, Action>(); public bool RegisterGlobalHotkey(Action action, Keys commonKey, params ModifierKeys[] keys) { uint mod = keys.Cast<uint>().Aggregate((current, modKey) => current | modKey); short atom = GlobalAddAtom("OurAmazingApp" + (_globalActions.Count + 1)); bool status = RegisterHotKey(_windowHandle, atom, mod, (uint)commonKey); if (status) { _globalActions.Add(atom, action); } return status; } 


And the implementation of WndProc:
 private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled) { if (msg == 0x0312) { short atom = Int16.Parse(wparam.ToString()); if (_globalActions.ContainsKey(atom)) { _globalActions[atom](); } } return IntPtr.Zero; } 


Now, with a slight movement of the hand, we can finally register something:
 RegisterGlobalHotkey(() => MessageBox.Show("!"), Keys.G, ModifierKeys.Alt, ModifierKeys.Control); 


Do not be in a hurry to rejoice, at the end of the application’s life, it would be better to delete the registration of our hotkeys so that they would not interfere with other applications. As mentioned above, this process is performed using the UnregisterHotKey and GlobalDeleteAtom functions. In our implementation, this can be done as follows:
 public void UnregisterHotkeys() { foreach(var atom in _globalActions.Keys) { UnregisterHotKey(_windowHandle, atom); GlobalDeleteAtom(atom); } } 


Everything, you can be happy - everything is implemented and works.

UPD: global hotkeys are meant at the OS level, not at the application level

UPD2:
Thanks to Karabasoff for the important addition:

When using RegisterHotKey and GlobalAddAtom, problems start as soon as the application begins to live only as a small icon deep in the tray. In this case, only the hooks are saved.
 [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr GetModuleHandle(string lpModuleName); //   private static IntPtr SetHook(LowLevelKeyboardProc proc) { using (var curProcess = Process.GetCurrentProcess()) { using (var curModule = curProcess.MainModule) { return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0); } } } //  ,  ,   private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN) { var vkCode = (Keys)Marshal.ReadInt32(lParam); switch (vkCode) { case Keys.MediaNextTrack: { break; } } } return CallNextHookEx(hookId, nCode, wParam, lParam); } 

Source: https://habr.com/ru/post/134158/


All Articles