前言
一切的起因就是Silverlight對F10鍵根本沒有響應。在按F10鍵時,根本不會觸發KeyDown事件。
Silverlight5之前的版本我不太清楚,不過Silverlight5新特性中有使用P/Invoke調用非托管代碼。既然這樣,做個鍵盤鈎子不就解決了?我喜歡DllImport。
正文
先了解鈎子相關的信息(SetWindowsHookEx、UnhookWindowsHookEx),下面是原生代碼。
1: private delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
2:
3: [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
4: private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
5:
6: [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
7: private static extern bool UnhookWindowsHookEx(int idHook);
8:
9: [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
10: private static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);
11:
12: [DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
13: private static extern IntPtr GetModuleHandle(string lpModuleName);
其中委托HookProc用於設置鈎子的回調函數。
處理回調的時候還會用到鍵盤鈎子的封送結構。
1: [StructLayout(LayoutKind.Sequential)]
2: private class KeyboardHookStruct
3: {
4: public int vkCode; //表示一個在1到254間的虛似鍵盤碼
5: public int scanCode;
6: public int flags;
7: public int time;
8: public int dwExtraInfo;
9: }
以下為主體代碼。
1: //常量
2: private const int WH_KEYBOARD_LL = 13; //低級鍵盤鈎子
3: private const int WM_KEYDOWN = 0x100;
4: private const int WM_SYSKEYDOWN = 0x104;
5:
6: private static int iKeyboardHook; //鈎子句柄
7: HookProc HookProcDelegate;
8:
9: //回調函數
10: [AllowReversePInvokeCalls]
11: private int HookProcHandler(int nCode, Int32 wParam, IntPtr lParam)
12: {
13: //可以增加當前窗口是否是活動窗口的判斷,不然低級鍵盤鈎子還會截獲所有的鍵盤消息
14: //可以使用App.Current.MainWindow.IsActive
15: KeyboardHookStruct khs = new KeyboardHookStruct();
16: Marshal.PtrToStructure(lParam, khs);
17: //KeyDown
18: if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
19: {
20: if (khs.vkCode == 0x79) //F10
21: {
22: //Do something
23: }
24: }
25: return CallNextHookEx(iKeyboardHook, nCode, wParam, lParam);
26: }
27:
28: /// <summary>
29: /// 注冊鍵盤鈎子
30: /// </summary>
31: public void Hook()
32: {
33: IntPtr ip = GetModuleHandle(System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0].Name);
34: HookProcDelegate = new HookProc(HookProcHandler);
35: iKeyboardHook = SetWindowsHookEx(13, HookProcDelegate, ip, 0);
36: if (iKeyboardHook == 0)
37: {
38: throw new Exception("注冊鍵盤鈎子失敗");
39: }
40: }
41:
42: /// <summary>
43: /// 卸載鍵盤鈎子
44: /// </summary>
45: public void UnHook()
46: {
47: if (!UnhookWindowsHookEx(iKeyboardHook))
48: {
49: throw new Exception("卸載鍵盤鈎子失敗");
50: }
51: iKeyboardHook = 0;
52: }
回調函數的AllowReversePInvokeCalls屬性允許非托管方法調用托管方法。
結語
現在截獲F10就是小意思了。還可以根據這些封裝一下,不過暫時不需要就先這樣了。
Tips:
1. 注冊鈎子時SetWindowsHookEx函數的threadId參數設置為0,所以需要在回調函數里判斷當前Silverlight窗體是否為活動窗體。
2. KeyboardHookStruct.vkCode對應的鍵值是System.Windows.Forms命名空間里Keys枚舉的鍵值,所以判斷是否是F10鍵時用的是0x79,而不是System.Windows.Input命名空間里Key枚舉的65。