全局热键是指系统范围内的按键事件,即无论代码在何处,只要用户按下了特定的按键组合,程序就可以做出响应。在Windows系统中,可以通过Desktop API提供的RegisterHotkey
函数来实现全局热键。然而,使用全局热键时需要注意以下几点限制:
1. 当注册一个按键时,实际上是覆盖了它原有的功能。例如,如果覆盖了F5键,用户可能会因为无法刷新浏览器页面而感到困扰。
2. 不同的程序不能注册相同的全局热键组合。如果尝试注册一个已经被其他运行中的程序占用的热键,会遇到WinAPI错误。
3. 有些按键组合是无法注册的,例如Ctrl+Alt+Del。
尽管低级键盘钩子可以绕过这些限制,但它有两个潜在的问题:
1. 代码可能会被杀毒软件标记为间谍软件(因为键盘记录)。
2. 它可能会减慢整个系统的键盘输入处理速度。
对于第一个限制,几乎无能为力,但第二个限制可以通过理解代码在做什么来避免。
当按下键盘上的一个键时,全局键盘钩子会在键盘输入到达最终目的地(即焦点应用程序)之前处理它。钩子是同步运行的。如果一个钩子运行缓慢,那么输入就会被延迟,这会让用户感到烦恼。
假设不想阻止任何按键到达其他钩子和程序,那么直观的解决方案是在单独的线程中处理输入。
以下是从开源库NonInvasiveKeyboardHook
中提取的LowLevelKeyboardProc
代码:
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
var vkCode = Marshal.ReadInt32(lParam);
// 为了防止减慢键盘输入,使用单独的线程处理键盘输入
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
确实会恢复当前按键的流程,但下一个按键的输入将不会被处理,直到阻塞代码完成并且函数返回。
如果目标是实现全局热键,而标准的WinAPI热键系统无法满足需求,那么开源的NonInvasiveKeyboardHook
库是一个很好的选择。它提供了一个KeyboardHookManager
,可以注册特定的按键组合(即不能将其用于间谍软件)。
建议只使用一个KeyboardHookManager
实例。
以下是如何使用该库的基本步骤:
1. 实例化并启动一个KeyboardHookManager
:
var keyboardHookManager = new KeyboardHookManager();
keyboardHookManager.Start();
2. 停止它(稍后可以通过调用.Start
再次启动):
keyboardHookManager.Stop();
3. 注册热键:
注册一个没有修饰键的热键:
keyboardHookManager.RegisterHotkey(0x60, () => {
Debug.WriteLine("NumPad0 detected");
});
注册一个带有单个修饰键的热键:
keyboardHookManager.RegisterHotkey(NonInvasiveKeyboardHookLibrary.ModifierKeys.Control, 0x60, () => {
Debug.WriteLine("Ctrl+NumPad0 detected");
});
注册一个带有多个修饰键的热键:
keyboardHookManager.RegisterHotkey(NonInvasiveKeyboardHookLibrary.ModifierKeys.Control | NonInvasiveKeyboardHookLibrary.ModifierKeys.Alt, 0x60, () => {
Debug.WriteLine("Ctrl+Alt+NumPad0 detected");
});
或者作为修饰键的枚举:
keyboardHookManager.RegisterHotkey(new[] {NonInvasiveKeyboardHookLibrary.ModifierKeys.Control, NonInvasiveKeyboardHookLibrary.ModifierKeys.Alt}, 0x60, () => {
Debug.WriteLine("Ctrl+Alt+NumPad0 detected");
});
4. 注销热键:
可以根据其唯一的按键组合或使用RegisterHotKey
返回的全局唯一标识符来注销热键:
keyboardHookManager.RegisterHotkey(0x60, () => { Debug.WriteLine("NumPad0 detected"); });
keyboardHookManager.UnregisterHotkey(0x60);
或者:
var hotkeyId = keyboardHookManager.RegisterHotkey(0x60, () => { Debug.WriteLine("NumPad0 detected"); });
keyboardHookManager.UnregisterHotkey(hotkeyId);
keyboardHookManager.UnregisterAll();