如何在WPF中捕获窗口外的事件

张开发
2026/4/10 21:43:26 15 分钟阅读

分享文章

如何在WPF中捕获窗口外的事件
捕获窗口消息关于窗口消息可以参考下面的文章https://www.cnblogs.com/zhaotianff/p/11285312.htmlhttps://www.cnblogs.com/zhaotianff/p/11297319.html在WPF中对于操作系统层面的原始输入 / 窗口消息如WM_LBUTTONDOWN、WM_MOUSEMOVE都定义了对应的事件。例如WM_LBUTTONDOWN对应WPFMouseLeftButtonDown事件、WM_MOUSEMOVE对应WPF的MouseMove事件。我们只需要添加事件处理函数就可以对这些Win32消息作出响应如下所示MainWindow.xaml1 Window MouseMoveWindow_MouseMove MouseLeftButtonDownWindow_MouseLeftButtonDown 2 3 /WindowMainWindow.xaml.cs1 private void Window_MouseMove(object sender, MouseEventArgs e) 2 { 3 4 } 5 6 private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) 7 { 8 9 }它的底层逻辑是通过接管所有Win32消息的模式来实现WPF会把这些Win32消息转换为对应的事件。通过添加事件处理函数就可以对这些操作系统层面的Win32消息进行处理。捕获窗口外的消息有时候我们想捕获窗口外的消息应该如何去操作呢。例如鼠标已经移出窗口外了但是我还是想知道鼠标何时按下。我们可以借助Win32 APIRegisterRawInputDevices和GetRawInputData函数来实现。RegisterRawInputDevices函数注册提供原始输入数据的设备。函数声明如下1 BOOL RegisterRawInputDevices( 2 [in] PCRAWINPUTDEVICE pRawInputDevices, 3 [in] UINT uiNumDevices, 4 [in] UINT cbSize 5 );参数pRawInputDevices指向一组RAWINPUTDEVICE结构代表提供原始输入的设备。uiNumDevicespRawInputDevices指向的RAWINPUTDEVICE结构的数量。cbSize指向RAWINPUTDEVICE结构的大小(以字节为单位)返回值如果函数成功则返回值为TRUE否则返回值为FALSE。GetRawInputData可以从指定的设备中获取原始输入GetRawInputData定义如下1 UINT GetRawInputData( 2 [in] HRAWINPUT hRawInput, 3 [in] UINT uiCommand, 4 [out, optional] LPVOID pData, 5 [in, out] PUINT pcbSize, 6 [in] UINT cbSizeHeader 7 );参数hRawInput指向RAWINPUT结构的句柄。它来自于WM_INPUT中的lParam。uiCommand它是命令标志。此参数可以是以下值之一。值含义RID_HEADER0x10000005从 RAWINPUT 结构获取标头信息。RID_INPUT0x10000003从 RAWINPUT 结构获取原始数据。pData指向来自RAWINPUT结构的数据指针这取决于uiCommand的值。如果pData为NULL则在* pcbSize中返回所需的缓冲区大小。cbSizeHeader指定RAWINPUTHEADER结构的大小以字节为单位。返回值如果pData为NULL且函数成功则返回值为零。如果pData不为空且函数成功则返回值为复制到pData中的字节数。如果有错误则返回值为UINT-1。这里还涉及了一个结构体RAWINPUTDEVICE这个结构体定义原始输入设备的信息RAWINPUTDEVICE定义如下1 typedef struct tagRAWINPUTDEVICE { 2 USHORT usUsagePage; //指向原始输入设备的顶级集合使用的页面。 3 USHORT usUsage; //指向原始输入设备的顶级集合的用法。 4 DWORD dwFlags; //指定如何解释由usUsagePage和usUsage提供的信息。它默认值为零默认情况下只要具有窗口焦点操作系统就会将具有顶级集合TLC设备的原始输入发送到已注册的应用程序中。 5 HWND hwndTarget; //指向目标窗口的句柄。如果是NULL则它会遵循键盘焦点。 6 } RAWINPUTDEVICE, *PRAWINPUTDEVICE, *LPRAWINPUTDEVICE;WPF中的实现步骤如下1、引用Win32 api函数及定义相应结构体1 namespace WPFGetRawInputData.Winapi 2 { 3 /// summary 4 /// 原始输入设备类型枚举 5 /// /summary 6 public enum RawInputType : uint 7 { 8 RIM_TYPEKEYBOARD 1, // 键盘 9 RIM_TYPEMOUSE 0 // 鼠标 10 } 11 12 /// summary 13 /// 原始输入设备结构体 14 /// /summary 15 [StructLayout(LayoutKind.Sequential)] 16 public struct RAWINPUTDEVICE 17 { 18 public ushort UsagePage; // 设备使用页键盘/鼠标固定值 19 public ushort Usage; // 设备使用ID键盘/鼠标固定值 20 public uint Flags; // 注册标志 21 public IntPtr WindowHandle; // 接收输入的窗口句柄 22 } 23 24 /// summary 25 /// 原始输入数据头部 26 /// /summary 27 [StructLayout(LayoutKind.Sequential)] 28 public struct RAWINPUTHEADER 29 { 30 public RawInputType Type; 31 public uint Size; 32 public IntPtr Device; 33 public IntPtr WParam; 34 } 35 36 /// summary 37 /// 原始键盘输入结构体 38 /// /summary 39 [StructLayout(LayoutKind.Sequential)] 40 public struct RAWKEYBOARD 41 { 42 public ushort MakeCode; 43 public ushort Flags; 44 public ushort Reserved; 45 public ushort VKey; 46 public uint Message; 47 public uint ExtraInformation; 48 } 49 50 /// summary 51 /// 原始鼠标输入结构体 52 /// /summary 53 [StructLayout(LayoutKind.Explicit,Size 4)] 54 public struct RAWMOUSE 55 { 56 [FieldOffset(0)] 57 public ushort Flags; 58 [FieldOffset(4)] 59 public uint Buttons; 60 [FieldOffset(4)] 61 public DUMMYSTRUCTNAME dUMMYSTRUCTNAME; 62 [FieldOffset(8)] 63 public uint RawButtons; 64 [FieldOffset(12)] 65 public int LastX; 66 [FieldOffset(16)] 67 public int LastY; 68 [FieldOffset(20)] 69 public uint ExtraInformation; 70 } 71 72 public struct DUMMYSTRUCTNAME 73 { 74 public ushort ButtonFlags; 75 public ushort ButtonData; 76 } 77 78 /// summary 79 /// 原始输入数据联合体键盘/鼠标二选一 80 /// /summary 81 [StructLayout(LayoutKind.Explicit)] 82 public struct RAWINPUTDATA 83 { 84 [FieldOffset(0)] 85 public RAWMOUSE Mouse; 86 [FieldOffset(0)] 87 public RAWKEYBOARD Keyboard; 88 } 89 90 /// summary 91 /// 原始输入结构体 92 /// /summary 93 [StructLayout(LayoutKind.Sequential)] 94 public struct RAWINPUT 95 { 96 public RAWINPUTHEADER Header; 97 public RAWINPUTDATA Data; 98 } 99 100 public static class User32 101 { 102 // 注册原始输入设备 103 [DllImport(user32.dll, SetLastError true)] 104 public static extern bool RegisterRawInputDevices( 105 [MarshalAs(UnmanagedType.LPArray, SizeParamIndex 0)] 106 RAWINPUTDEVICE[] pRawInputDevices, 107 uint uiNumDevices, 108 uint cbSize); 109 110 // 获取原始输入数据 111 [DllImport(user32.dll, SetLastError true)] 112 public static extern uint GetRawInputData( 113 IntPtr hRawInput, 114 uint uiCommand, 115 IntPtr pData, 116 ref uint pcbSize, 117 uint cbSizeHeader); 118 119 // 窗口消息常量 120 public const uint WM_INPUT 0x00FF; 121 122 // 键盘使用页/ID 123 public const ushort HID_USAGE_PAGE_GENERIC 0x01; 124 public const ushort HID_USAGE_GENERIC_KEYBOARD 0x06; 125 126 // 鼠标使用页/ID 127 public const ushort HID_USAGE_GENERIC_MOUSE 0x02; 128 129 // 注册标志输入数据发送到窗口消息队列 130 public const uint RIDEV_INPUTSINK 0x00000100; 131 } 132 }注意这里有个非常大的坑在定义RAWMOUSE时要注意联合体以及4字节对齐问题。关于联合体在P/Invoke时的封送可以参考https://www.cnblogs.com/zhaotianff/p/13949849.html说明推荐使用nuget包Cswin32可以参考我前面的文章https://www.cnblogs.com/zhaotianff/p/186579032、注册原始输入设备1 //注册 2 RAWINPUTDEVICE[] devices new RAWINPUTDEVICE[2]; 3 4 // 注册键盘 5 devices[0] new RAWINPUTDEVICE 6 { 7 UsagePage User32.HID_USAGE_PAGE_GENERIC, 8 Usage User32.HID_USAGE_GENERIC_KEYBOARD, 9 Flags User32.RIDEV_INPUTSINK, 10 WindowHandle mainWindowHandle 11 }; 12 13 // 注册鼠标 14 devices[1] new RAWINPUTDEVICE 15 { 16 UsagePage User32.HID_USAGE_PAGE_GENERIC, 17 Usage User32.HID_USAGE_GENERIC_MOUSE, 18 Flags User32.RIDEV_INPUTSINK, 19 WindowHandle mainWindowHandle 20 }; 21 22 // 调用API注册设备 23 var result User32.RegisterRawInputDevices( 24 devices, 25 (uint)devices.Length, 26 (uint)Marshal.SizeOf(typeof(RAWINPUTDEVICE))); 27 28 if (result false) 29 { 30 System.Windows.MessageBox.Show(注册失败); 31 32 //调用GetLastError查看原因 33 } 34 else 35 { 36 DisplayMessage(注册成功); 37 }3、添加Win32消息捕获1 protected override void OnSourceInitialized(EventArgs e) 2 { 3 base.OnSourceInitialized(e); 4 5 mainWindowHandle new WindowInteropHelper(this).Handle; 6 HwndSource.FromHwnd(mainWindowHandle).AddHook(HwndProc); 7 } 8 9 public IntPtr HwndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) 10 { 11 // 处理WM_INPUT消息 12 if (msg User32.WM_INPUT) 13 { 14 //处理原始输入数据 15 ProcessRawInput(lParam); 16 handled true; 17 } 18 return IntPtr.Zero; 19 }4、获取原始数据1 /// summary 2 /// 解析原始输入数据 3 /// /summary 4 /// param namelParam/param 5 private void ProcessRawInput(IntPtr lParam) 6 { 7 uint dataSize 0; 8 // 第一步获取数据大小 9 User32.GetRawInputData( 10 lParam, 11 0x10000003, // RID_INPUT获取原始输入数据 12 IntPtr.Zero, 13 ref dataSize, 14 (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER))); 15 16 if (dataSize 0) return; 17 18 // 第二步分配内存并获取数据 19 IntPtr dataPtr Marshal.AllocHGlobal((int)dataSize); 20 try 21 { 22 uint result User32.GetRawInputData( 23 lParam, 24 0x10000003, 25 dataPtr, 26 ref dataSize, 27 (uint)Marshal.SizeOf(typeof(RAWINPUTHEADER))); 28 29 if (result ! dataSize) return; 30 31 // 第三步解析数据 32 RAWINPUT rawInput Marshal.PtrToStructureRAWINPUT(dataPtr); 33 switch (rawInput.Header.Type) 34 { 35 //键盘 36 case RawInputType.RIM_TYPEKEYBOARD: 37 ProcessKeyboardInput(rawInput.Data.Keyboard); 38 break; 39 //鼠标 40 case RawInputType.RIM_TYPEMOUSE: 41 ProcessMouseInput(rawInput.Data.Mouse); 42 break; 43 } 44 } 45 finally 46 { 47 Marshal.FreeHGlobal(dataPtr); 48 } 49 } 50 51 /// summary 52 /// 处理键盘输入 53 /// /summary 54 /// param namekeyboard/param 55 private void ProcessKeyboardInput(RAWKEYBOARD keyboard) 56 { 57 // 判断按键按下Flags0或释放Flags1 58 bool isKeyDown (keyboard.Flags 0x01) 0; 59 60 // 转换为键盘按键 61 System.Windows.Forms.Keys key (System.Windows.Forms.Keys)keyboard.VKey; 62 63 // 输出调试信息可替换为自定义逻辑 64 string action isKeyDown ? 按下 : 释放; 65 66 DisplayMessage($键盘{key} {action} (扫描码{keyboard.MakeCode})); 67 } 68 69 // 处理鼠标输入 70 private void ProcessMouseInput(RAWMOUSE mouse) 71 { 72 // 鼠标按键状态 73 bool leftButtonDown (mouse.dUMMYSTRUCTNAME.ButtonFlags 0x0001) ! 0; 74 bool leftButtonUp (mouse.dUMMYSTRUCTNAME.ButtonFlags 0x0002) ! 0; 75 bool rightButtonDown (mouse.dUMMYSTRUCTNAME.ButtonFlags 0x0004) ! 0; 76 bool rightButtonUp (mouse.dUMMYSTRUCTNAME.ButtonFlags 0x0008) ! 0; 77 78 // 输出调试信息可替换为自定义逻辑 79 if (leftButtonDown) 80 { 81 DisplayMessage(鼠标左键按下); 82 } 83 84 if (leftButtonUp) 85 { 86 DisplayMessage(鼠标左键释放); 87 } 88 89 if (rightButtonDown) 90 { 91 DisplayMessage(鼠标右键按下); 92 } 93 94 if (rightButtonUp) 95 { 96 DisplayMessage(鼠标右键释放); 97 } 98 }运行效果

更多文章