桌面应用程序中的全局热键





5.00/5 (7投票s)
了解如何在 C# 桌面应用程序(例如 Windows Forms 或 WPF)中正确创建全局热键
引言
实现全局热键有两种常见方法,每种方法都有其自身的优缺点。当您的代码脱离程序的上下文并进入用户的桌面时,了解您的代码正在做什么非常重要。
背景
全局热键是指系统范围内的按键事件,即,无论键盘焦点在哪里,您的代码都会对按键做出反应的情况。
Windows 桌面 API 公开了 RegisterHotkey 函数,您应该优先使用它,而不是使用 低级键盘钩子的替代方法。
但是,RegisterHotkey
有 三个主要的限制
- 当您注册一个键时,您基本上会覆盖其原始功能。例如,如果您覆盖了 F5 键,当该键不再在他们的浏览器上刷新网页时,您的用户会感到恼火。
- 在第 1 点之后,不言而喻的是,没有两个程序可以具有相同的全局热键组合。如果您尝试注册已属于另一个正在运行的程序的热键,您将遇到 WinAPI 错误。
- 某些组合根本无法注册(例如 Ctrl+Alt+Del)
低级键盘钩子克服了这些限制,但您需要记住两个后果
- 您的代码很可能会被杀毒软件(或对您的代码进行反编译并看到您使用
SetWindowsHookEx
的人)标记为间谍软件(用于键盘记录) - 它有可能减慢整个系统的键盘输入处理速度(这将在下面详细解释)
对于第一个限制,我们几乎无能为力,但如果您了解自己在做什么,则可以防止后者发生。
首先,让我们看看当您按下键盘上的一个键时会发生什么
(对于 KEYUP
也是类似的过程)
从上面的图表中,您了解到您的全局键盘钩子会在键盘输入到达其最终目的地(焦点应用程序)之前处理键盘输入。
钩子同步运行。如果钩子运行缓慢,则输入将被延迟并惹恼用户。
假设您不打算阻止任何键到达其他钩子和程序,则此问题的直观解决方案是在单独的线程中处理输入。
这是从开源 NonInvasiveKeyboardHook 库中提取的 LowLevelKeyboardProc
代码
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
var vkCode = Marshal.ReadInt32(lParam);
// To prevent slowing keyboard input down, we use handle keyboard inputs in a separate thread
ThreadPool.QueueUserWorkItem
(this.HandleSingleKeyboardInput, new KeyboardParams(wParam, vkCode));
}
return CallNextHookEx(_hookId, nCode, wParam, lParam);
}
一种幼稚的方法可能有点颠倒顺序,例如
var returnValue = CallNextHookEx(_hookId, nCode, wParam, lParam);
ProcessInputInSameThread();
return returnValue;
虽然尽早调用 CallNextHookEx
确实会恢复 当前 键在消息队列中的流程,但只有在阻塞代码完成并且该函数返回后,才会处理 下一个 按键。
Using the Code
因此,如果您想要全局热键,并且标准的 WinAPI 热键系统不能满足您的需求,那么开源 NonInvasiveKeyboardHook 库就是答案。
它公开了一个 KeyboardHookManager
,它公开了注册特定键组合的功能(即,您不能将其用于间谍软件)。
* 建议您仅使用 一个 KeyboardHookManager
实例。
基础
实例化并启动一个 KeyboardHookManager
var keyboardHookManager = new KeyboardHookManager();
keyboardHookManager.Start();
停止它(稍后可以通过再次调用 .Start
来恢复)。
keyboardHookManager.Stop();
注册热键
注册一个没有修饰符的 hotkey
// 0x60 = NumPad0
keyboardHookManager.RegisterHotkey(0x60, () =>
{
Debug.WriteLine("NumPad0 detected");
});
注册一个带单个修饰符的 hotkey
keyboardHookManager.RegisterHotkey(NonInvasiveKeyboardHookLibrary.ModifierKeys.Control, 0x60, () =>
{
Debug.WriteLine("Ctrl+NumPad0 detected");
});
注册一个带有多个修饰符的 hotkey
// Multiple modifiers can be specified using the bitwise OR operation
keyboardHookManager.RegisterHotkey(NonInvasiveKeyboardHookLibrary.ModifierKeys.Control |
NonInvasiveKeyboardHookLibrary.ModifierKeys.Alt, 0x60, () =>
{
Debug.WriteLine("Ctrl+Alt+NumPad0 detected");
});
// Or as an enum of modifiers
keyboardHookManager.RegisterHotkey(new[]
{NonInvasiveKeyboardHookLibrary.ModifierKeys.Control,
NonInvasiveKeyboardHookLibrary.ModifierKeys.Alt}, 0x60, () =>
{
Debug.WriteLine("Ctrl+Alt+NumPad0 detected");
});
取消注册热键
可以根据其唯一的键组合或使用 RegisterHotKey
返回的全局唯一标识符来注销 hotkey
。
keyboardHookManager.RegisterHotkey(0x60, () => { Debug.WriteLine("NumPad0 detected"); });
keyboardHookManager.UnregisterHotkey(0x60);
或者
var hotkeyId = keyboardHookManager.RegisterHotkey(0x60, () => { Debug.WriteLine("NumPad0 detected"); });
keyboardHookManager.UnregisterHotkey(hotkeyId);
也可以注销 所有 hotkey
keyboardHookManager.UnregisterAll();