<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      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。

      posted @ 2025-10-16 11:07  mingupupup  閱讀(336)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 无码毛片一区二区本码视频| 人妻丝袜中文无码av影音先锋| 国产午夜福利视频合集| 亚洲日本乱码熟妇色精品| 隆化县| 日韩加勒比一本无码精品| 南郑县| 国产成人精品18| 日韩精品永久免费播放平台| 无码国产偷倩在线播放老年人| 国产精品一区二区三区91| 亚洲一区二区三区四区| 亚洲av日韩av永久无码电影| 亚洲国产在一区二区三区| 精品久久欧美熟妇www| 人妻无码久久精品| 亚洲精品一区二区口爆| 亚洲综合另类小说色区一| 免费久久人人爽人人爽AV| 97av麻豆蜜桃一区二区| 亚洲一区在线成人av| 国内自拍视频在线一区| 亚洲中文欧美在线视频| 2019国产精品青青草原| 内射毛片内射国产夫妻| 精品国产午夜福利在线观看| 成人影片麻豆国产影片免费观看| 国产高清在线男人的天堂| 午夜福利免费视频一区二区| 男女啪啪高潮激烈免费版| 在线观看精品日本一区二| 亚洲成在人线在线播放无码| 嘉兴市| 国内精品亚洲成av人片| 99热成人精品热久久66| 成人精品日韩专区在线观看 | 野花社区www视频日本| 色偷偷亚洲男人的天堂| 99久久激情国产精品| 神马午夜久久精品人妻| 97在线视频人妻无码|