当前位置: 首页 > news >正文

WPF应用绑定系统快捷键

全局快捷键的应用

在现代桌面应用开发中,全局快捷键功能是提升用户体验的重要手段。用户无需将焦点切换到应用窗口,就能通过特定的键盘组合快速触发应用功能。本文以Rouyan,开源地址:https://github.com/Ming-jiayou/Rouyan为例,说明在WPF应用中可以如何绑定系统快捷键。

全局键盘钩子

Rouyan中是在 KeySequenceService.cs 中实现的,全局键盘钩子通过 Windows API 实现,允许应用程序监听系统级的键盘事件,而不受窗口焦点限制。

1、Win32 API 导入

类中导入了必要的 Windows API 函数:

SetWindowsHookEx:安装钩子

UnhookWindowsHookEx:卸载钩子

CallNextHookEx:将钩子传递给下一个处理器

GetModuleHandle:获取模块句柄

[DllImport("user32.dll")]
public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);[DllImport("user32.dll")]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);[DllImport("user32.dll")]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);[DllImport("kernel32.dll")]
public static extern IntPtr GetModuleHandle(string lpModuleName);

现在先来学习一下这几个函数:

1、SetWindowsHookEx

[DllImport("user32.dll")]
public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

用途:安装钩子过程到钩子链中。钩子允许应用程序拦截和处理系统消息或事件。

参数:

idHook (int):钩子类型。对于低级键盘钩子,使用常量 WH_KEYBOARD_LL = 13。

lpfn (LowLevelKeyboardProc):指向钩子过程的指针。在代码中传递 HookCallback 方法。

hMod (IntPtr):包含钩子过程的模块句柄。使用 GetModuleHandle 获取当前模块句柄。

dwThreadId (uint):要关联钩子的线程 ID。设为 0 表示全局钩子(所有线程)。

返回值:成功时返回钩子句柄 (IntPtr),失败时返回 IntPtr.Zero。

在代码中的应用:在 SetHook 方法中调用,用于安装低级键盘钩子,使应用程序能监听系统级键盘事件。

2、UnhookWindowsHookEx

[DllImport("user32.dll")]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);

用途:从钩子链中移除指定的钩子过程。必须在使用完毕后调用,以释放系统资源。

参数:

hhk (IntPtr):要移除的钩子句柄。由 SetWindowsHookEx 返回。
返回值:成功时返回 true,失败时返回 false。

在代码中的应用:在 Dispose 方法中调用,确保应用程序退出时正确卸载钩子,避免内存泄漏和系统级问题。

3、CallNextHookEx

[DllImport("user32.dll")]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

用途:将钩子信息传递给钩子链中的下一个钩子过程。这是钩子链机制的核心,确保所有钩子都能处理消息。

参数:

hhk (IntPtr):当前钩子的句柄(可选,通常设为当前钩子句柄)。

nCode (int):钩子代码,指示如何处理消息。

wParam (IntPtr):消息的 WPARAM 参数。

lParam (IntPtr):消息的 LPARAM 参数。

返回值:下一个钩子过程的返回值。

在代码中的应用:在 HookCallback 方法末尾调用,确保处理完自定义逻辑后,将消息传递给系统或其他钩子。

4、GetModuleHandle

[DllImport("kernel32.dll")]
public static extern IntPtr GetModuleHandle(string lpModuleName);

用途:检索指定模块的模块句柄。模块句柄用于标识 DLL 或 EXE 文件。

参数:

lpModuleName (string):模块名称(不含路径)。如果为 null,返回调用进程的主模块句柄。

返回值:成功时返回模块句柄 (IntPtr),失败时返回 IntPtr.Zero。

在代码中的应用:在 SetHook 方法中调用,获取当前进程主模块的句柄,作为 SetWindowsHookEx 的 hMod 参数,用于关联钩子到当前应用程序模块。

具体实现

先总体看一下KeySequenceService类做了什么?

1、注册/卸载全局键盘钩子

2、拦截按键并用状态机识别序列

3、将“Tab + 字母”组合映射到 8 个动作

4、保持系统钩子链

2-4就是在钩子回调中做的事。

一些常量设置

        // 低级键盘钩子常量private const int WH_KEYBOARD_LL = 13;private const int WM_KEYDOWN = 0x0100;// 按键常量(Tab + 字母 序列)private const int VK_TAB = 0x09;private const int VK_K = 0x4B;private const int VK_L = 0x4C;private const int VK_U = 0x55;private const int VK_I = 0x49;private const int VK_S = 0x53;private const int VK_D = 0x44;private const int VK_W = 0x57;private const int VK_E = 0x45;// 序列超时时间(毫秒)private const int SEQUENCE_TIMEOUT_MS = 2000;

private const int WH_KEYBOARD_LL = 13;

含义:Win32 的“低级键盘钩子”类型常量。用于安装系统范围的键盘事件回调。

低级键盘钩子是什么意思?

“低级键盘钩子”(WH_KEYBOARD_LL)是 Windows 提供的一种全局键盘事件拦截机制。通过 Win32 API 在用户态安装后,系统在键盘事件产生时会优先回调你提供的函数,让你的程序有机会观察、处理,甚至拦截按键,再将事件传递给系统或其他钩子。

用途:作为 SetWindowsHookEx 的 idHook 参数,安装键盘钩子。

private const int WM_KEYDOWN = 0x0100;

含义:键盘“按下”消息常量。

用途:在钩子回调中过滤只处理按下事件。

剩下的是虚拟键码与序列超时时间。

注册/卸载全局键盘钩子

构造阶段:准备钩子回调与委托防 GC

public delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);public KeySequenceService(){    _proc = HookCallback;}private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam){if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN){int vkCode = Marshal.ReadInt32(lParam);HandleKeyDown(vkCode);}return CallNextHookEx(_hookID, nCode, wParam, lParam);}

HookCallback 的作用是作为 WH_KEYBOARD_LL 低级键盘钩子的回调入口,按键事件一到达就被它截获、筛选并转交给序列状态机处理,最后把事件继续传给系统的下一枚钩子。

注册键盘钩子:

 public void RegisterHotKeys(){try{_hookID = SetHook(_proc);if (_hookID == IntPtr.Zero){Console.WriteLine("警告: 无法安装全局键盘钩子");}else{Console.WriteLine("全局热键已注册:\n" +"Tab+K (RunLLMPrompt1)\n" +"Tab+L (RunLLMPrompt1Streaming)\n" +"Tab+U (RunLLMPrompt2)\n" +"Tab+I (RunLLMPrompt2Streaming)\n" +"Tab+S (RunVLMPrompt1)\n" +"Tab+D (RunVLMPrompt1Streaming)\n" +"Tab+W (RunVLMPrompt2)\n" +"Tab+E (RunVLMPrompt2Streaming)");}}catch (Exception ex){Console.WriteLine($"注册热键失败: {ex.Message}");}}private IntPtr SetHook(LowLevelKeyboardProc proc){using var curProcess = System.Diagnostics.Process.GetCurrentProcess();using var curModule = curProcess.MainModule;if (curModule?.ModuleName != null){return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);}return IntPtr.Zero;}

其中核心代码是 return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);

意思是安装低级键盘钩子并返回钩子句柄,proc就是钩子的回调方法,然后传入当前这个模块,0表示对系统范围内所有线程生效(全局钩子)。

卸载键盘钩子:

 public void Dispose(){try{if (_hookID != IntPtr.Zero){UnhookWindowsHookEx(_hookID);_hookID = IntPtr.Zero;Console.WriteLine("全局热键已卸载");}}catch (Exception ex){Console.WriteLine($"清理热键资源时出错: {ex.Message}");}}

钩子回调

private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN){int vkCode = Marshal.ReadInt32(lParam);HandleKeyDown(vkCode);}return CallNextHookEx(_hookID, nCode, wParam, lParam);
}private void HandleKeyDown(int vkCode)
{switch (_currentMode){case HotkeyMode.None:if (vkCode == VK_TAB){_currentMode = HotkeyMode.WaitingAfterTab;_sequenceStartTime = DateTime.Now;Console.WriteLine("检测到 Tab 键,等待按下后续字母键...");}break;case HotkeyMode.WaitingAfterTab:if (IsTimeout()){Console.WriteLine("按键序列超时");}else{switch (vkCode){case VK_K:Console.WriteLine("检测到完整组合键 Tab+K,执行 RunLLMPrompt1...");ExecuteAction(_runLLMPrompt1);break;case VK_L:Console.WriteLine("检测到完整组合键 Tab+L,执行 RunLLMPrompt1Streaming...");ExecuteAction(_runLLMPrompt1Streaming);break;case VK_U:Console.WriteLine("检测到完整组合键 Tab+U,执行 RunLLMPrompt2...");ExecuteAction(_runLLMPrompt2);break;case VK_I:Console.WriteLine("检测到完整组合键 Tab+I,执行 RunLLMPrompt2Streaming...");ExecuteAction(_runLLMPrompt2Streaming);break;case VK_S:Console.WriteLine("检测到完整组合键 Tab+S,执行 RunVLMPrompt1...");ExecuteAction(_runVLMPrompt1);break;case VK_D:Console.WriteLine("检测到完整组合键 Tab+D,执行 RunVLMPrompt1Streaming...");ExecuteAction(_runVLMPrompt1Streaming);break;case VK_W:Console.WriteLine("检测到完整组合键 Tab+W,执行 RunVLMPrompt2...");ExecuteAction(_runVLMPrompt2);break;case VK_E:Console.WriteLine("检测到完整组合键 Tab+E,执行 RunVLMPrompt2Streaming...");ExecuteAction(_runVLMPrompt2Streaming);break;default:Console.WriteLine($"检测到 Tab 后的无效按键: {vkCode}");break;}}ResetState();break;}// 检查超时并重置状态if (_currentMode != HotkeyMode.None && IsTimeout()){Console.WriteLine("按键序列超时");ResetState();}
}

只处理键盘按下消息类型,然后根据不同的快捷键组合调用不同的方法。

private void ExecuteAction(Action action)
{try{// 在UI线程上执行操作Application.Current?.Dispatcher.BeginInvoke(new Action(() =>{try{action?.Invoke();}catch (Exception ex){Console.WriteLine($"执行热键操作时出错: {ex.Message}");}}), DispatcherPriority.Normal);}catch (Exception ex){Console.WriteLine($"调度热键操作时出错: {ex.Message}");}
}

在HotkeyService中对热键做了管理:

 /// <summary>/// 初始化热键服务/// </summary>/// <param name="mainWindow">主窗口</param>public void Initialize(Window mainWindow){try{// 初始化Tab+字母组合键服务_keySequenceService = new KeySequenceService(ExecuteRunLLMPrompt1,ExecuteRunLLMPrompt1Streaming,ExecuteRunLLMPrompt2,ExecuteRunLLMPrompt2Streaming,ExecuteRunVLMPrompt1,ExecuteRunVLMPrompt1Streaming,ExecuteRunVLMPrompt2,ExecuteRunVLMPrompt2Streaming);_keySequenceService.RegisterHotKeys();// 初始化全局ESC键服务_globalEscService = new GlobalEscService();_globalEscService.Register();}catch (Exception ex){Console.WriteLine($"初始化热键服务失败: {ex.Message}");}}

把具体要执行的方法传进去:

/// <summary>
/// 执行RunLLMPrompt1操作
/// 当检测到 Tab+K 组合键时调用
/// </summary>
private async void ExecuteRunLLMPrompt1()
{try{var homeViewModel = _container.Get<HomeViewModel>();if (homeViewModel != null){await homeViewModel.RunLLMPrompt1();}else{Console.WriteLine("警告: 无法获取HomeViewModel实例");}}catch (Exception ex){Console.WriteLine($"执行Tab+K热键操作失败: {ex.Message}");}
}

在Bootstrapper中初始化这个热键服务:

 protected override void OnLaunch(){    // 初始化和获取全局快捷键服务try{var _hotkeyService = this.Container.Get<HotkeyService>();if (Application.Current?.MainWindow != null){_hotkeyService.Initialize(Application.Current.MainWindow);}}catch (Exception ex){Console.WriteLine($"初始化全局快捷键失败: {ex.Message}");}}

然后就成功实现了按下设定的快捷键就会触发特定的方法。

用Rouyan举个例子就是按下tab + l快捷键时,就会自动弹出流式窗口,根据提示词的内容,对剪贴板中的内容进行处理,如下所示:

然后按下esc就会关闭这个窗口,实现思路是一样的,代码我写到了GlobalEscService中,关键代码如下所示:

 private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam){if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN){int vkCode = Marshal.ReadInt32(lParam);// 检查是否按下了ESC键if (vkCode == VK_ESCAPE){// 查找并关闭ShowMessageView窗口CloseShowMessageWindow();}}return CallNextHookEx(_hookID, nCode, wParam, lParam);}/// <summary>/// 查找并关闭ShowMessageView窗口/// </summary>private void CloseShowMessageWindow(){// 在UI线程上执行窗口查找和关闭操作Application.Current.Dispatcher.Invoke(() =>{// 遍历所有打开的窗口foreach (Window window in Application.Current.Windows){// 检查是否是ShowMessageView类型的窗口if (window is Rouyan.Pages.View.ShowMessageView showMessageWindow){showMessageWindow.Close();break; // 找到并关闭后退出循环}}});}

以上就是本期分享的全部内容,希望对你有所帮助,如果对具体实现感兴趣欢迎查看Rouyan代码,开源地址:https://github.com/Ming-jiayou/Rouyan。

http://www.hskmm.com/?act=detail&tid=32175

相关文章:

  • 2025年篷布厂家最新推荐排行榜,多功能防水篷布、聚乙烯篷布、帐篷/汽车/宴会盖布、盖草布、泳池布、微喷水带、日用盖布、农林用篷布、重型机器用篷布公司精选
  • 2025年轻钢龙骨/铝方通/铝单板/石膏板厂家最新权威推荐榜单:专业生产与品质保障深度解析
  • 2025年彩钢瓦/镀锌板/折弯件/C型钢/Z型钢/压型瓦/楼承板/次檩条厂家最新推荐排行榜,钢结构安装服务与金属构件生产实力深度解析
  • 2025年发电机组厂家最新权威推荐榜:柴油/燃气/船用/静音箱式/移动拖车/集装箱式,涵盖上柴/玉柴/潍柴/康明斯/沃尔沃/道依茨/帕金斯/MTU品牌
  • 程序员面试、算法研究、机器学习、大模型/ChatGPT/AIGC、论文审稿、具身智能/人形机器人、RAG等20大系列集锦
  • 2025年精密磨床/CNC加工厂家最新权威推荐榜:涵盖车床/铣床/多轴/复合加工,铝/不锈钢/钛合金/模具钢/塑料件定制,专攻汽车/医疗/航空航天/机器人零件及注塑模具
  • 2025 年最新推荐导轨丝杆源头厂家排行榜:聚焦优质货源,助力企业精准选品直线/滚珠/孚雷/恒而达导轨丝杆厂家推荐
  • 有没有什么比较好用的拼图工具?
  • 2025年鸡精生产线厂家最新权威推荐榜:高速混合机/WDG农药生产线/鸡粉干燥设备/海鲜精干燥设备/调味料干燥成套系统专业解析
  • 2025年法兰保护罩厂家最新推荐排行榜,阀门保温罩,法兰罩,法兰防溅罩,法兰保护套,专业定制与防护性能深度解析
  • 2025 年南昌装修公司推荐:南昌宿然设计 —— 无营销套路专注落地还原的技术型装修设计机构
  • 杂题记录2
  • 每日坚持读一段英文,熟悉英文表达-2025-10-16
  • 【运维自动化-标准运维】各类全局变量使用说明(下)
  • 英语_阅读_Travel widely_待读
  • 2025年恒温恒湿系统厂家最新权威推荐榜:精加工车间/厂房/美术馆/仓库/计算机房/档案室/工厂车间恒温恒湿环境解决方案专业解析
  • 2025年鸡精生产线厂家最新推荐排行榜,高速混合机,WDG农药生产线,鸡粉/海鲜精干燥设备,调味料干燥设备,全自动配料,鸡精干燥成套设备,螺带混合机公司推荐
  • 10 16
  • Gitee Pipe:重塑关键领域DevSecOps生态的智能引擎
  • 2025 苏州注册公司服务机构实用推荐:5 家靠谱机构帮初创者少走弯路
  • ESP32-C5来袭,双频Wi-Fi 6 + BLE 5.0 + Zigbee三线合一
  • 2025年铝单板厂家最新推荐排行榜,幕墙铝单板,双曲铝单板,冲孔铝单板,雕花铝单板,异形铝单板公司精选
  • 【照片GPS批量导出工具】,一键导入,秒出Excel!
  • VkDescriptorSetLayout的用途是什么?是如何工作的
  • 2025 年国内本安电源源头厂家最新推荐排行榜:聚焦 12V/24V/5V 防爆电源,助力企业精准选优质供应商
  • 2025年粉末冶金制品/零件厂家最新权威推荐榜:电机轴承、单向轴承、含油轴承、自润滑轴承源头供应商精选
  • MSSQL 恢复到时间点方法
  • 【C4D精品资源】iPhone17系列全家桶3D模型源文件:含动画场景+OC材质全预设
  • 2025 土工布厂家推荐榜:山东鸿跃环保—— 从水利到基建,防水土工布/长丝土工布/短丝土工布/防渗土工布适配全需求
  • LLM学习记录DAY2