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 continue; 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 continue; 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 continue; 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 static bool UndoMonitorHotkey(int id) 7 { 8 if (hotkeyList != null && hotkeyList.Count > 0) 9 { 10 for (int i = 0; i < hotkeyList.Count; i++) 11 { 12 if (id == 0) 13 { 14 hotkeyList[i].Id = 0; 15 } 16 else 17 { 18 if (id == hotkeyList[i].Id) 19 { 20 hotkeyList[i].Id = 0; 21 return true; 22 } 23 } 24 } 25 } 26 return id == 0; 27 }
------------------------------------------------------------------功能代碼已完成,下面開始調用---------------------------------------------------------------------------------------
七、調用
點擊注冊熱鍵
鍵盤按下字母鍵盤 1 (在桌面任何一個界面都可實現監控)
撤銷熱鍵就是用注冊時的返回值傳入封裝的UndoMonitorHotkey() 即可撤銷,我這里就不演示了。
附上一個鍵碼對照表地址:http://www.atoolbox.net/Tool.php?Id=815
最后總結: 其實本方案監視熱鍵實現過程原理是:創建一個系統定時器線程,不斷的獲取每個按鍵的狀態是否被按下,如果被注冊的鍵狀態是被按下的 則回調傳入的事件來實現的。