1. 程式人生 > 其它 >C# 實現全域性監視熱鍵( 鍵盤按下事件)

C# 實現全域性監視熱鍵( 鍵盤按下事件)

C# 實現全域性監控鍵盤點選事件

 

記錄一下實現在C#程式以外的介面也能實現鍵盤按下並執行對應的事件的實現方式。

 

  由於公司有一個專案,需要註冊熱鍵來實現全域性檢測按鍵才能完成該功能。 winfrom中的鍵盤點選事件又只能焦點在程式視窗上才能實現,這種達不到我想要的效果。

  我在網上找了很多案例都讓我不是很滿意,效果也不是特別好。 無意間從一個論壇中找到一個易語言編寫的監視熱鍵編譯好的模組,但C#並不能直接呼叫這個模組,我就建立了一個易語言程式,並把呼叫這個易模組然後編譯為dll後給C#呼叫。然後我發現效果還是挺好的。但是兩個弊端:

  1.易語言編譯的檔案只支援32位的(C#程式64位就無法呼叫)。 *問題最嚴重

  2.總覺得一個基礎的功能 還必須依賴外部dll 個人心裡感受不是那麼好,而且這個也無法直接整合到專案程式碼去,dll依賴太強了。。 

  由於第一個原因,我的程式是64位的,這個模組監視熱鍵又確實挺好用,我就想著把他翻譯成C#程式碼,就方便使用了。然後就索性在論壇找了個工具,把這個易語言模組給反編譯了,看下他內部邏輯咋寫的。

  這是反編譯前的易模組:

  

 

  反編譯後:

  

 

 

  開啟檔案,檢視程式碼 (反編譯後雖然邏輯程式碼都出來了,但是變數命名就是亂的,看的我眼花繚亂):

  這些是一部分邏輯程式碼。

 

 

 

 

 

 

  接下來就開始幹活,把它翻譯成C#語言來實現此功能,之後就想怎麼用就怎麼用了。

 

一、建立一個Win32Api類

 

 

 

 

 二、定義windowsApi

 1         /// <summary>
 2         /// 建立一個定時器
 3         /// </summary>
 4         /// <param name="hWnd">程式控制代碼,為空則為系統級定時器</param>
 5         /// <param name="nIDEvent">定時器ID</param>
 6         /// <param name="uElapse">毫秒週期</param>
7 /// <param name="lpTimerFunc">定時器觸發事件</param> 8 /// <returns></returns> 9 [DllImport("user32.dll")] 10 private static extern int SetTimer(int hWnd, int nIDEvent, int uElapse, Action lpTimerFunc); 11 12 /// <summary> 13 /// 銷燬定時器 14 /// </summary> 15 /// <param name="hWnd">程式控制代碼,為空則為系統級定時器</param> 16 /// <param name="nIDEvent">定時器ID, 若SetTimer的hWnd為0,則必須傳SetTimer的返回值</param> 17 /// <returns></returns> 18 [DllImport("user32.dll")] 19 private static extern int KillTimer(int hWnd, int nIDEvent); 20 21 /// <summary> 22 /// 確定在呼叫函式時某個鍵是向上還是向下,以及在上一次呼叫GetAsyncKeyState之後是否按下了該鍵。 23 /// </summary> 24 /// <param name="keyCode"></param> 25 /// <returns></returns> 26 [DllImport("user32.dll")] 27 private static extern Int16 GetAsyncKeyState(int keyCode); 28 29 /// <summary> 30 /// 將訊息資訊傳遞給指定的視窗過程。 回撥鉤子 31 /// </summary> 32 /// <param name="lpPrevWndFunc"></param> 33 /// <param name="hWnd"></param> 34 /// <param name="Msg"></param> 35 /// <param name="wParam"></param> 36 /// <param name="lParam"></param> 37 /// <returns></returns> 38 [DllImport("user32.dll")] 39 private static extern int CallWindowProcA(Action lpPrevWndFunc, int hWnd, int Msg, int wParam, int lParam); 40 41 /// <summary> 42 /// 關閉開啟的物件控制代碼。 43 /// </summary> 44 /// <param name="hObject"></param> 45 /// <returns></returns> 46 [DllImport("kernel32.dll")] 47 private static extern int CloseHandle(int hObject); 48 49 /// <summary> 50 /// 建立一個執行緒以在呼叫程序的虛擬地址空間內執行。 51 /// </summary> 52 /// <param name="lpThreadAttributes"></param> 53 /// <param name="dwStackSize"></param> 54 /// <param name="lpStartAddress"></param> 55 /// <param name="lpParameter"></param> 56 /// <param name="dwCreationFlags"></param> 57 /// <param name="lpThreadId"></param> 58 /// <returns></returns> 59 [DllImport("kernel32.dll")] 60 private static extern int CreateThread(int lpThreadAttributes, int dwStackSize, Action lpStartAddress, int lpParameter, int dwCreationFlags, int lpThreadId);

 

三、定義一個熱鍵資訊類

 1         /// <summary>
 2         /// 熱鍵資訊
 3         /// </summary>
 4         public class Hotkey
 5         {
 6             public int Id { get; set; }
 7 
 8             public Action Action { get; set; }
 9 
10             public int KeyCode { get; set; }
11 
12             public int FunKeyCode { get; set; }
13 
14             public int OtherKeyCode { get; set; }
15 
16             public byte KeyState { get; set; }
17 
18             public bool State { get; set; }
19 
20             public bool DirectTrigger { get; set; }
21         }

 

三、定義兩個靜態物件:事件、註冊的熱鍵集合 (必須為靜態全域性物件,防止被GC回收)

1         private static List<Hotkey> hotkeyList = null; //記錄註冊的熱鍵資訊
2         private static Action _HotkeyAction = null;  //監視熱鍵執行緒

 

四、封裝方法供外部呼叫實現熱鍵註冊

 1         /// <summary>
 2         /// 註冊熱鍵
 3         /// </summary>
 4         /// <param name="action">響應事件</param>
 5         /// <param name="keyCode">鍵程式碼</param>
 6         /// <param name="funKeyCode">功能鍵程式碼  1 Alt  2 Ctrl  4 Shift  8 Win 若要兩個或以上的狀態鍵,則把它們的值相加.</param>
 7         /// <param name="otherKeyCode">如果需要註冊由兩個普通鍵組合的熱鍵,可設定一個其它鍵程式碼.</param>
 8         /// <param name="millisecondsTimeout">預設為10,監視熱鍵的週期時間(建議5-200之間)</param>
 9         /// <param name="DirectTrigger">預設為false:建立新的執行緒事件 true:直接呼叫事件等待返回</param>
10         /// <returns></returns>
11         public static int RegisterHotkey(Action action, int keyCode, int funKeyCode = 0, int otherKeyCode = 0, int millisecondsTimeout = 10, bool DirectTrigger = false)
12         {
13             Hotkey hotkey = new Hotkey();
14 
15             if (keyCode <= 0)
16                 return 0;
17             if (hotkeyList == null)
18                 hotkeyList = new List<Hotkey>();
19 
20             for (int i = 0; i < hotkeyList.Count; i++)
21             {
22                 if (hotkeyList[i].KeyCode == keyCode && hotkeyList[i].FunKeyCode == funKeyCode && hotkeyList[i].OtherKeyCode == otherKeyCode)
23                 {
24                     hotkeyList[i].Action = action;
25                     hotkeyList[i].DirectTrigger = DirectTrigger;
26 
27                     if (hotkeyList[i].Id != 0)
28                     {
29                         return hotkeyList[i].Id;
30                     }
31 
32                     hotkeyList[i].Id = i + 1000000;
33                     return hotkeyList[i].Id;
34                 }
35             }
36 
37             hotkey.Action = action;
38             hotkey.KeyCode = keyCode;
39             hotkey.FunKeyCode = funKeyCode;
40             hotkey.OtherKeyCode = otherKeyCode;
41             hotkey.DirectTrigger = DirectTrigger;
42             hotkey.Id = hotkeyList.Count + 1000001;
43             hotkeyList.Add(hotkey);
44 
45             if (hotkey.Id == 1000001)
46             {
47                 _HotkeyAction = MonitorHotkeyThreads;
48 
49                 int time = millisecondsTimeout == 0 ? 10 : millisecondsTimeout;
50 
51                 // 建立定時器
52                 SetTimer(_HotkeyAction, time);
53             }
54 
55             return hotkey.Id;
56         }

 

五、建立監視註冊熱鍵的執行緒方法

 1        /// <summary>
 2         /// 監視註冊熱鍵的執行緒
 3         /// </summary>
 4         public static void MonitorHotkeyThreads()
 5         {
 6             Action tempAction = null;
 7             int tempId = 0;
 8 
 9             Int16[] cacheKeyState = new Int16[256];
10 
11             for (int i = 0; i < 255; i++)
12             {
13                 cacheKeyState[i] = 251;
14                 cacheKeyState[i] = GetAsyncKeyState(i);
15             }
16 
17             for (int i = 0; i < hotkeyList.Count; i++)
18             {
19                 if (hotkeyList[i].Id != 0)
20                 {
21                     int k = hotkeyList[i].KeyCode;
22                     k = cacheKeyState[k];
23 
24                     if (k == 0)  //0表示無狀態
25                     {
26                         if (hotkeyList[i].KeyState == 1)
27                         {
28                             hotkeyList[i].KeyState = 2;
29                         }
30                         else
31                         {
32                             hotkeyList[i].KeyState = 0;
33                         }
34                         break;
35                     }
36                     if (k < 0)  //-32767,按下狀態
37                     {
38                         if (hotkeyList[i].KeyState == 0)
39                         {
40                             hotkeyList[i].KeyState = 1;
41                         }
42                         if (hotkeyList[i].KeyCode < 0)
43                         {
44                             break;
45                         }
46                     }
47 
48                     // 判斷啟用熱鍵
49                     if (hotkeyList[i].KeyState > 0 && hotkeyList[i].KeyState != 88)
50                     {
51                         hotkeyList[i].KeyState = 88;
52 
53                         int funNum = cacheKeyState[18] < 0 ? 1 : 0;
54                         funNum += cacheKeyState[17] < 0 ? 2 : 0;
55                         funNum += cacheKeyState[16] < 0 ? 4 : 0;
56                         funNum += cacheKeyState[91] < 0 ? 8 : 0;
57 
58                         if (hotkeyList[i].FunKeyCode == funNum)
59                         {
60                             if (hotkeyList[i].OtherKeyCode != 0)
61                             {
62                                 k = hotkeyList[i].OtherKeyCode;
63                                 if (cacheKeyState[k] >= 0)
64                                 {
65                                     break;
66                                 }
67                             }
68 
69                             tempAction = hotkeyList[i].Action;
70                             tempId = hotkeyList[i].Id;
71 
72                             if (hotkeyList[i].DirectTrigger)
73                             {
74                                 CallWindowProcA(tempAction, tempId, 0, 0, 0);
75                             }
76                             else
77                             {
78                                 CloseHandle(CreateThread(0, 0, tempAction, tempId, 0, 0));
79                             }
80                         }
81                     }
82 
83                 }
84             }
85         }

 

六、封裝撤銷監視熱鍵的方法

 1         /// <summary>
 2         /// 撤銷監視熱鍵
 3         /// </summary>
 4         /// <param name="id">撤消的熱鍵標識(註冊時的返回值) ,如果留空則撤消全部熱鍵</param>
 5         /// <returns></returns>
 6         public bool UndoMonitorHotkey(int id)
 7         {
 8             for (int i = 0; i < hotkeyList.Count; i++)
 9             {
10                 if (id == 0)
11                 {
12                     hotkeyList[i].Id = 0;
13                 }
14                 else 
15                 {
16                     if (id == hotkeyList[i].Id) 
17                     {
18                         hotkeyList[i].Id = 0;
19                         return true;
20                     }
21                 }
22             }
23             return id == 0;
24         }

 

------------------------------------------------------------------功能程式碼已完成,下面開始呼叫---------------------------------------------------------------------------------------

 

七、呼叫

點選註冊熱鍵

 

 鍵盤按下字母鍵盤 1  (在桌面任何一個介面都可實現監控)

 

 

 撤銷熱鍵就是用註冊時的返回值傳入封裝的UndoMonitorHotkey() 即可撤銷,我這裡就不演示了。

 

 附上一個鍵碼對照表地址:http://www.atoolbox.net/Tool.php?Id=815

 

最後總結: 其實本方案監視熱鍵實現過程原理是:建立一個系統定時器執行緒,不斷的獲取每個按鍵的狀態是否被按下,如果被註冊的鍵狀態是被按下的 則回撥傳入的事件來實現的。