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 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
最后总结: 其实本方案监视热键实现过程原理是:创建一个系统定时器线程,不断的获取每个按键的状态是否被按下,如果被注册的键状态是被按下的 则回调传入的事件来实现的。