























关于窗口消息,可以参考下面的文章
https://www.cnblogs.com/zhaotianff/p/11285312.html
https://www.cnblogs.com/zhaotianff/p/11297319.html
在WPF中,对于操作系统层面的原始输入 / 窗口消息,如 WM_LBUTTONDOWN、WM_MOUSEMOVE,都定义了对应的事件。
例如:WM_LBUTTONDOWN对应WPFMouseLeftButtonDown事件、WM_MOUSEMOVE对应WPF的MouseMove事件。
我们只需要添加事件处理函数,就可以对这些Win32消息作出响应,如下所示:
MainWindow.xaml
1 <Window MouseMove="Window_MouseMove" MouseLeftButtonDown="Window_MouseLeftButtonDown" > 2 3 </Window>
MainWindow.xaml.cs
1 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 API RegisterRawInputDevices和 GetRawInputData函数来实现。
RegisterRawInputDevices函数注册提供原始输入数据的设备。
函数声明如下:
1 BOOL RegisterRawInputDevices( 2 [in] PCRAWINPUTDEVICE pRawInputDevices, 3 [in] UINT uiNumDevices, 4 [in] UINT cbSize 5 );
参数
pRawInputDevices
指向一组RAWINPUTDEVICE结构,代表提供原始输入的设备。
uiNumDevices
pRawInputDevices指向的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
它是命令标志。此参数可以是以下值之一。
| 值 | 含义 |
|---|---|
|
从 RAWINPUT 结构获取标头信息。 |
|
从 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 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/18657903
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 }
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 }
1 /// <summary> 2 /// 解析原始输入数据 3 /// </summary> 4 /// <param name="lParam"></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.PtrToStructure<RAWINPUT>(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 name="keyboard"></param> 55 private void ProcessKeyboardInput(RAWKEYBOARD keyboard) 56 { 57 // 判断按键按下(Flags=0)或释放(Flags=1) 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 }

此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。