WndProc处理WM_MOUSEACTIVATE时如何获取鼠标实时按键状态
实现左键点击窗口时不激活切换到目标窗口的效果,可通过处理WM_MOUSEACTIVATE消息完成。实际开发中出现异常:不做鼠标按键状态校验时逻辑运行正常,加入按键状态检测后,即便触发消息时鼠标左键处于按住状态,通过WPF内置的Mouse.LeftButton属性读取到的按键状态始终为已释放。
对应异常实现代码:
private const int WM_MOUSEACTIVATE = 0x0021; private const uint MA_NOACTIVATE = 3; private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { if (msg == WM_MOUSEACTIVATE) { if (Mouse.LeftButton == MouseButtonState.Pressed) { handled = true; return (IntPtr)MA_NOACTIVATE ;
排查确认消息时序:WM_LBUTTONDOWN消息的接收时机晚于WM_MOUSEACTIVATE。原本预期静态属性Mouse.LeftButton可以不受消息时序影响返回鼠标实时状态,需要更可靠的鼠标左键状态获取方案。
WPF的Mouse.LeftButton属性维护的按键状态,完全依赖WPF自身消息循环接收到的鼠标消息更新。WM_MOUSEACTIVATE是窗口激活流程中极早触发的消息,发送时机早于客户区的WM_LBUTTONDOWN消息,此时WPF还未收到左键按下的通知,内部状态没有更新,自然会返回按键已释放的结果。
直接调用Windows原生APIGetAsyncKeyState读取硬件层面的实时按键状态,该API不依赖调用线程所属窗口的消息队列时序,可以直接获取当前物理按键的真实状态,不受WM_MOUSEACTIVATE和WM_LBUTTONDOWN的先后顺序影响。
具体实现
- 声明需要调用的Win32 API及虚拟键码常量
- 在
WM_MOUSEACTIVATE处理逻辑中,替换原有的Mouse.LeftButton判断,通过API返回值判断左键是否按下:返回值最高位为1时,代表按键当前处于按下状态
完整可运行代码:
// Win32 API导入 [System.Runtime.InteropServices.DllImport("user32.dll")] private static extern short GetAsyncKeyState(int vKey); private const int VK_LBUTTON = 0x01; private const int WM_MOUSEACTIVATE = 0x0021; private const uint MA_NOACTIVATE = 3; private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { if (msg == WM_MOUSEACTIVATE) { // 校验返回值最高位判断按键是否按下 bool leftButtonPressed = (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0; if (leftButtonPressed) { handled = true; return (IntPtr)MA_NOACTIVATE; } } return IntPtr.Zero; }
补充说明:
GetAsyncKeyState读取的是调用瞬间的全局按键状态,极端场景下如果调用瞬间刚好有其他输入事件触发按键状态变更,可能存在极微小的时序误差,但在WM_MOUSEACTIVATE触发的时间点,该方案的可靠性远高于依赖WPF内部状态的Mouse.LeftButton属性,完全满足当前场景的使用需求。
内容的提问来源于stack exchange,提问作者Daniel

