如何与失焦窗口交互?替换ControlSend为SendMessage/PostMessage实现动态窗口交互需求
实现悬停时向非活动窗口发送输入(替换ControlSend为SendMessage/PostMessage)
我完全理解你的需求:指定一个目标窗口,当鼠标悬停在它上面时,把所有鼠标点击和按键输入都转发给它,类似Win10的「悬停滚动非活动窗口」功能,还要摆脱ControlSend对控件类名的依赖,改用更通用的SendMessage/PostMessage来适配不同类型的窗口。咱们一步步来解决这个问题。
为什么ControlSend不适合你的场景?
ControlSend需要明确指定窗口内的控件类名(比如记事本的Edit1),但不同软件的控件结构千差万别——Chrome的输入框、邮件客户端的编辑器类名都不一样,通用性极差。而SendMessage/PostMessage是直接向窗口句柄发送系统消息,不需要关注内部控件,刚好适配你的多窗口需求。
核心实现思路
- 快速捕获目标窗口:保留你原来的热键选窗口逻辑,快速指定要控制的目标。
- 全局拦截输入事件:通过Windows低级钩子,全局捕获所有按键和鼠标点击操作。
- 转换并转发消息:当鼠标悬停在目标窗口上时,把捕获到的输入转换成对应的Windows系统消息(键盘用
WM_KEYDOWN/WM_KEYUP/WM_CHAR,鼠标用WM_LBUTTONDOWN等),通过PostMessage发送给目标窗口,同时要把屏幕坐标转换成窗口内部的客户区坐标。
修改后的完整代码
#include <Misc.au3> #include <WinAPI.au3> #include <WinAPISysWin.au3> #include <WinAPIProc.au3> ; 全局变量:目标窗口句柄、钩子句柄 Global $g_hTargetWnd = 0 Global $g_hKeyboardHook = 0 Global $g_hMouseHook = 0 ; 热键设置:`键选中目标窗口,F1查看当前目标,Esc退出脚本 HotKeySet("`", "_SelectTargetWindow") HotKeySet("{F1}", "_ShowCurrentTarget") HotKeySet("{Esc}", "_ExitScript") ; 安装全局低级钩子,捕获所有输入 $g_hKeyboardHook = _WinAPI_SetWindowsHookEx($WH_KEYBOARD_LL, "_KeyboardProc") $g_hMouseHook = _WinAPI_SetWindowsHookEx($WH_MOUSE_LL, "_MouseProc") ; 主循环保持脚本运行 While 1 Sleep(100) WEnd ; --- 核心功能函数 --- Func _SelectTargetWindow() ; 获取鼠标指向的窗口,递归找到顶层父窗口(避免选中子控件) Local $tMousePos = _WinAPI_GetMousePos() Local $hWnd = _WinAPI_WindowFromPoint($tMousePos) While _WinAPI_GetParent($hWnd) <> 0 $hWnd = _WinAPI_GetParent($hWnd) WEnd $g_hTargetWnd = $hWnd MsgBox(0, "目标已设置", "当前控制窗口:" & WinGetTitle($hWnd)) EndFunc Func _ShowCurrentTarget() If $g_hTargetWnd = 0 Then MsgBox(0, "提示", "还没设置目标窗口!按`键选择") Else MsgBox(0, "当前目标", "窗口标题:" & WinGetTitle($g_hTargetWnd) & @CRLF & "窗口类名:" & _WinAPI_GetClassName($g_hTargetWnd)) EndIf EndFunc Func _KeyboardProc($nCode, $wParam, $lParam) ; 钩子处理失败则直接传递消息 If $nCode < 0 Then Return _WinAPI_CallNextHookEx($g_hKeyboardHook, $nCode, $wParam, $lParam) ; 仅当鼠标悬停在目标窗口上时处理输入 Local $tMousePos = _WinAPI_GetMousePos() Local $hHoverWnd = _WinAPI_WindowFromPoint($tMousePos) While _WinAPI_GetParent($hHoverWnd) <> 0 $hHoverWnd = _WinAPI_GetParent($hHoverWnd) WEnd If $hHoverWnd <> $g_hTargetWnd Then Return _WinAPI_CallNextHookEx($g_hKeyboardHook, $nCode, $wParam, $lParam) ; 解析键盘消息结构体 Local $tKBHook = DllStructCreate($tagKBDLLHOOKSTRUCT, $lParam) Local $nVKCode = DllStructGetData($tKBHook, "vkCode") Local $nScanCode = DllStructGetData($tKBHook, "scanCode") Local $nFlags = DllStructGetData($tKBHook, "flags") ; 发送键盘消息到目标窗口 If $wParam = $WM_KEYDOWN Or $wParam = $WM_SYSKEYDOWN Then ; 发送按键按下消息 _WinAPI_PostMessage($g_hTargetWnd, $wParam, $nVKCode, BitOR($nScanCode, BitShift($nFlags, 16))) ; 发送字符消息(处理大小写、符号等实际输入) Local $nChar = _WinAPI_MapVirtualKey($nVKCode, $MAPVK_VK_TO_CHAR) If $nChar <> 0 Then _WinAPI_PostMessage($g_hTargetWnd, $WM_CHAR, $nChar, 0) EndIf ElseIf $wParam = $WM_KEYUP Or $wParam = $WM_SYSKEYUP Then ; 发送按键抬起消息 _WinAPI_PostMessage($g_hTargetWnd, $wParam, $nVKCode, BitOR($nScanCode, BitShift($nFlags, 16))) EndIf ; 拦截原始消息,避免输入到当前焦点窗口 Return 1 EndFunc Func _MouseProc($nCode, $wParam, $lParam) If $nCode < 0 Then Return _WinAPI_CallNextHookEx($g_hMouseHook, $nCode, $wParam, $lParam) ; 仅当鼠标悬停在目标窗口上时处理 Local $tMousePos = _WinAPI_GetMousePos() Local $hHoverWnd = _WinAPI_WindowFromPoint($tMousePos) While _WinAPI_GetParent($hHoverWnd) <> 0 $hHoverWnd = _WinAPI_GetParent($hHoverWnd) WEnd If $hHoverWnd <> $g_hTargetWnd Then Return _WinAPI_CallNextHookEx($g_hMouseHook, $nCode, $wParam, $lParam) ; 解析鼠标消息结构体 Local $tMouseHook = DllStructCreate($tagMSLLHOOKSTRUCT, $lParam) Local $nX = DllStructGetData($tMouseHook, "x") Local $nY = DllStructGetData($tMouseHook, "y") ; 把屏幕坐标转换成目标窗口的客户区坐标(Windows鼠标消息用的是窗口内坐标) Local $tClientPos = _WinAPI_ScreenToClient($g_hTargetWnd, $nX, $nY) Local $nClientX = DllStructGetData($tClientPos, "X") Local $nClientY = DllStructGetData($tClientPos, "Y") ; 组合坐标到lParam参数(低16位存X,高16位存Y) Local $nLParam = BitOR(BitShift($nClientY, 16), BitAND($nClientX, 0xFFFF)) ; 发送对应鼠标消息 Switch $wParam Case $WM_LBUTTONDOWN _WinAPI_PostMessage($g_hTargetWnd, $WM_LBUTTONDOWN, $MK_LBUTTON, $nLParam) Case $WM_LBUTTONUP _WinAPI_PostMessage($g_hTargetWnd, $WM_LBUTTONUP, 0, $nLParam) Case $WM_RBUTTONDOWN _WinAPI_PostMessage($g_hTargetWnd, $WM_RBUTTONDOWN, $MK_RBUTTON, $nLParam) Case $WM_RBUTTONUP _WinAPI_PostMessage($g_hTargetWnd, $WM_RBUTTONUP, 0, $nLParam) Case $WM_MOUSEMOVE ; 可选:转发鼠标移动消息,让窗口感知鼠标位置 _WinAPI_PostMessage($g_hTargetWnd, $WM_MOUSEMOVE, 0, $nLParam) EndSwitch ; 拦截原始鼠标消息 Return 1 EndFunc Func _ExitScript() ; 卸载钩子,避免内存泄漏 _WinAPI_UnhookWindowsHookEx($g_hKeyboardHook) _WinAPI_UnhookWindowsHookEx($g_hMouseHook) Exit EndFunc
代码关键细节说明
- 全局低级钩子:用
_WinAPI_SetWindowsHookEx安装键盘/鼠标钩子,能全局捕获所有输入事件,不依赖当前焦点窗口。 - 坐标转换:
_WinAPI_ScreenToClient把屏幕坐标转换成目标窗口内部的客户区坐标,因为Windows鼠标消息的坐标基准是窗口左上角,不是屏幕左上角。 - 消息类型选择:
- 键盘:
WM_KEYDOWN/WM_KEYUP处理按键的按下/抬起,WM_CHAR处理实际字符输入(比如区分大小写、特殊符号)。 - 鼠标:
WM_LBUTTONDOWN/WM_LBUTTONUP对应左键点击,右键则用WM_RBUTTON系列消息。
- 键盘:
- 拦截原始消息:返回1表示不把原始输入传递给系统,确保只有目标窗口收到输入,不会干扰当前焦点窗口。
使用步骤
- 运行脚本后,按
``键(反引号),鼠标指向你要控制的窗口(比如记事本、Chrome、邮件客户端),松开按键就完成目标设置。 - 鼠标悬停在目标窗口上时,所有按键输入和鼠标点击都会发送给它,即使窗口处于失焦状态。
- 按
F1可以查看当前设置的目标窗口信息,按Esc安全退出脚本。
这个方案完美解决了ControlSend的控件依赖问题,支持绝大多数Windows窗口,完全符合你的需求。
内容的提问来源于stack exchange,提问作者Kevin P.




