WPFを使用してグローバルホットキーを登録する

おそらく、アプリケーションがグローバルキーを介して何かを制御できるようにしたいと思ったことがあるかもしれません。 また、WPFテクノロジを使用したプログラミングが好きかもしれません。 次に、このトピックはあなたのためです。





この問題を解決するには、Windowsでホットキーメカニズムがどのように機能するかを理解する必要があります。ホットキーメカニズムを直接操作するWPFメソッドはないためです。 したがって、WinAPIにアクセスする必要があります。

以下の機能が必要になります。



ホットキー登録:

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







ホットキーの削除:

 BOOL WINAPI UnregisterHotKey( __in_opt HWND hWnd, __in int id );
      
      







ホットキーを識別し、その識別子(アトム)を取得するための一意の文字列の登録:

 ATOM GlobalAddAtom( LPCTSTR lpString );
      
      







そして、それに応じて、原子の除去:

 ATOM WINAPI GlobalDeleteAtom( __in ATOM nAtom );
      
      







実際、メカニズム自体は非常に単純です-ホットキー識別文字列を登録し、結果のアトムを使用してホットキー自体を登録します。 アプリケーションを終了したら、ホットキーとアトムの登録を削除します-それだけです。 ご覧のとおり、非常に簡単です。 それでは実装に移りましょう。



C#で、これらの関数を対応するdllからエクスポートします。

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







1つの小さな問題がここに表示されます-WndProc呼び出し処理。 実際、WPFでは、Windowsフォームとは異なり、アプリケーションウィンドウでこの関数をオーバーロードすることはできません。 ただし、次のようにWndProc呼び出しを処理できます。

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







ホットキーを登録する準備は万全です。 たとえば、クラスに次のメソッドを追加します。

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







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







これで、手首を軽く動かすだけで、最終的に何かを登録できます。

 RegisterGlobalHotkey(() => MessageBox.Show("!"), Keys.G, ModifierKeys.Alt, ModifierKeys.Control);
      
      







急いで喜ばないでください。アプリケーションの寿命の終わりに、他のアプリケーションに干渉しないようにホットキーを登録解除する方が良いでしょう。 前述のように、このプロセスはUnregisterHotKeyおよびGlobalDeleteAtom関数を使用して実行されます。 実装では、これは次のように実行できます。

 public void UnregisterHotkeys() { foreach(var atom in _globalActions.Keys) { UnregisterHotKey(_windowHandle, atom); GlobalDeleteAtom(atom); } }
      
      







すべて、あなたは喜ぶことができます-すべてが実装され、動作しています。



UPD:アプリケーションレベルではなく、OSレベルでグローバルな「ホットキー」を指す



UPD2:

重要な追加についてKarabasoff感謝します



RegisterHotKeyとGlobalAddAtomを使用する場合、アプリケーションがトレイの奥深くにある小さなアイコンの形でのみ生き始めるとすぐに問題が始まります。 この場合、フックのみが保存されます。

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








All Articles