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