You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何与失焦窗口交互?替换ControlSend为SendMessage/PostMessage实现动态窗口交互需求

实现悬停时向非活动窗口发送输入(替换ControlSend为SendMessage/PostMessage)

我完全理解你的需求:指定一个目标窗口,当鼠标悬停在它上面时,把所有鼠标点击和按键输入都转发给它,类似Win10的「悬停滚动非活动窗口」功能,还要摆脱ControlSend对控件类名的依赖,改用更通用的SendMessage/PostMessage来适配不同类型的窗口。咱们一步步来解决这个问题。

为什么ControlSend不适合你的场景?

ControlSend需要明确指定窗口内的控件类名(比如记事本的Edit1),但不同软件的控件结构千差万别——Chrome的输入框、邮件客户端的编辑器类名都不一样,通用性极差。而SendMessage/PostMessage是直接向窗口句柄发送系统消息,不需要关注内部控件,刚好适配你的多窗口需求。

核心实现思路

  1. 快速捕获目标窗口:保留你原来的热键选窗口逻辑,快速指定要控制的目标。
  2. 全局拦截输入事件:通过Windows低级钩子,全局捕获所有按键和鼠标点击操作。
  3. 转换并转发消息:当鼠标悬停在目标窗口上时,把捕获到的输入转换成对应的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

代码关键细节说明

  1. 全局低级钩子:用_WinAPI_SetWindowsHookEx安装键盘/鼠标钩子,能全局捕获所有输入事件,不依赖当前焦点窗口。
  2. 坐标转换_WinAPI_ScreenToClient把屏幕坐标转换成目标窗口内部的客户区坐标,因为Windows鼠标消息的坐标基准是窗口左上角,不是屏幕左上角。
  3. 消息类型选择
    • 键盘:WM_KEYDOWN/WM_KEYUP处理按键的按下/抬起,WM_CHAR处理实际字符输入(比如区分大小写、特殊符号)。
    • 鼠标:WM_LBUTTONDOWN/WM_LBUTTONUP对应左键点击,右键则用WM_RBUTTON系列消息。
  4. 拦截原始消息:返回1表示不把原始输入传递给系统,确保只有目标窗口收到输入,不会干扰当前焦点窗口。

使用步骤

  1. 运行脚本后,按``键(反引号),鼠标指向你要控制的窗口(比如记事本、Chrome、邮件客户端),松开按键就完成目标设置。
  2. 鼠标悬停在目标窗口上时,所有按键输入和鼠标点击都会发送给它,即使窗口处于失焦状态。
  3. F1可以查看当前设置的目标窗口信息,按Esc安全退出脚本。

这个方案完美解决了ControlSend的控件依赖问题,支持绝大多数Windows窗口,完全符合你的需求。

内容的提问来源于stack exchange,提问作者Kevin P.

火山引擎 最新活动