OnMessage

当脚本接收到指定消息时, 会自动调用指定的函数.

OnMessage MsgNumber , Function, MaxThreads

参数

MsgNumber

类型: 整数

需要监听或查询的消息编号, 应该介于 0 和 4294967295(0xFFFFFFFF) 之间. 如果你不想监听系统消息(即编号小于 0x0400 的那些), 那么最好选择一个大于 4096(0x1000) 的数字. 这降低了可能对当前及将来版本的 AutoHotkey 内部所使用的消息的干扰.

Function

类型: 函数对象

当接收到消息时要调用的函数对象. 函数的参数和返回值如所示.

MaxThreads

类型: 整数

这个整数通常被省略, 在这种情况下, 监控函数一次只能处理一个线程. 这通常是最好的, 因为否则每当监控函数中断时, 脚本就会按时间顺序处理消息. 因此, 作为 MaxThreads 的替代方案, 可以考虑使用 Critical, 如所示.

如果监控函数直接或间接导致消息再次被发送, 而该函数仍在运行, 则有必要指定一个大于 1 或小于 -1 的 MaxThreads 值, 以允许监控函数为了新消息而被调用(如果需要). 脚本自己的进程向自己发送(不是发布) 的消息不能被延迟或缓冲.

指定 0 来取消注册之前由 Function 标识的函数.

默认情况下, 当为一个 MsgNumber 注册了多个函数时, 会按照注册的顺序调用它们. 要在之前注册的函数之前注册一个函数, 请为 MaxThreads 指定一个负值. 例如, OnMessage Msg, Fn, -2Fn 注册为在之前为 Msg 注册的任何其他函数之前被调用, 并且允许 Fn 最多有 2 个线程. 但是, 如果函数已经被注册了, 除非取消注册然后重新注册, 否则顺序不会改变.

使用

任何数量的函数或函数对象都可以监视一个给定的 MsgNumber.

这两行中的任何一行都会注册一个函数对象, 以便在任何先前注册的函数 之后 被调用:

OnMessage MsgNumber, Function     ; 选项 1 - 省略 MaxThreads
OnMessage MsgNumber, Function, 1  ; 选项 2 - 指定 MaxThreads 为 1

注册一个函数对象, 以便在任何先前注册的函数 之前 被调用:

OnMessage MsgNumber, Function, -1

要取消注册一个函数对象, 请将 MaxThreads 指定为 0:

OnMessage MsgNumber, Function, 0

函数的参数

函数可以监视一个或多个消息并应该接受四个参数:

FunctionName(wParam, lParam, msg, hwnd)

尽管您赋予参数的名称并不重要, 但以下信息将按顺序分配给它们:

WPARAM 和 LPARAM 是无符号的 32 位整数(从 0 到 232-1) 或有符号的 64 位整数(从 -263 到 263-1), 这取决于运行脚本的 exe 程序是 32 位还是 64 位. 对于 32 位的脚本, 如果传入参数预期为有符号整数, 则可以参照这个例子得到负数:

if (A_PtrSize = 4 && wParam > 0x7FFFFFFF)  ; 检查 A_PtrSize 以确保脚本为 32 位.
    wParam := -(~wParam) - 1

如果不需要相应的信息, 你可以从列表末尾省略一个或多个参数, 但在这种情况下, 必须指定一个星号作为最后的参数. 例如, 一个定义为 MyMsgMonitor(wParam, lParam, *) 函数将只接收前两个参数, 而定义为 MyMsgMonitor(*) 的函数将不接收任何参数.

函数中可用的附加信息

除了上面接收到的参数外, 该函数还可以查询内置变量 A_EventInfo, 如果消息是通过 SendMessage 发送的, 则其为 0. 如果是通过 PostMessage 发送的, 则其为消息发出时的 tick-count 时间.

监听函数的上次找到的窗口初始与消息发送到的父窗口相同(即使消息是发送到控件的). 如果窗口是隐藏的, 但不是 GUI 窗口(例如脚本的主窗口), 在使用它之前打开 DetectHiddenWindows. 例如:

DetectHiddenWindows True
MsgParentWindow := WinExist()  ; 这里保存了消息发送的目标窗口的唯一 ID.

函数应该 Return(返回) 什么

如果监听函数使用不带任何参数的 Return, 或指定空值如 ""(甚至没有使用 Return), 则当此函数结束时, 传入的消息将继续被正常处理. 同样的情况也会出现在函数使用 Exit 或者出现了运行时错误的时候(例如, 运行不存在的文件). 与此相反, 返回一个整数时会被作为回复立即发送; 即程序不会再进一步处理此消息. 例如, 监听 WM_LBUTTONDOWN(0x0201) 的函数可以返回一个整数来阻止目标窗口接收到鼠标点击的通知. 在许多情况下(例如使用 PostMessage 发送的消息), 返回哪个整数并不重要; 不过如果不确定, 0 通常是最安全的.

有效返回值的范围取决于运行脚本的 exe 文件是 32 位还是 64 位. 对于 32 位(A_PtrSize = 4) 脚本, 非空的返回值必须在 -231 和 232-1 之间, 对于 64 位(A_PtrSize = 8) 脚本, 必须在 -263 和 263-1 之间.

如果有多个函数监听一个给定的消息号, 它们会被逐一调用, 直到其中一个函数返回一个非空值.

备注

与普通的函数调用不同, 当一个被监听的消息到达时, 函数会作为一个新的线程被调用. 因此, 函数会以设置的默认值启动, 例如 SendModeDetectHiddenWindows. 这个默认设置可以通过在脚本启动中使用此函数来改变.

Send(发送)(而不是 Post) 到控件的消息不会被监听, 因为系统直接把它们发送给后台的控件了. 对于系统生成的消息来说, 这很少是一个问题, 因为大多数信息都是 Post 的.

如果脚本要在空闲状态下保持运行, 以监测传入的消息, 可能需要调用 Persistent 函数来防止脚本退出. OnMessage 不会自动使脚本持续运行, 因为这有时是不必要的或不需要的. 例如, 当 OnMessage 被用来监视 GUI 窗口的输入时(如在 WM_LBUTTONDOWN 的例子), 通常让脚本在最后一个 GUI 窗口关闭时自动退出更为合适.

当一个消息达到时, 如果其函数由于之前到达的相同消息而仍在运行, 默认情况下, 该函数将不会被再次调用; 相反, 该消息将被视为不受监控. 如果不希望这样, 有多种方法可以避免它:

如果在脚本不可中断的情况下, Post 一条数值大于 0x0311 的监控信息, 那么这条信息就会被缓冲; 也就是说, 在脚本变得可中断之前, 它的函数不会被调用. 然而, sent 而不是 posted 的消息不能被缓冲, 因为它们必须提供一个返回值. Post 的消息也可能不被缓冲, 当一个模式消息循环在运行时, 如系统对话框, ListView 拖放操作或菜单.

如果一个被监控的消息到达并且没有被缓冲, 那么它的函数会被立即调用, 即使在收到消息的时候线程是不可中断的.

OnMessage 的优先级总是为 0. 因此, 如果当前线程的优先级大于 0 时将不会监听或缓冲任何消息.

监听系统消息(小于 0x0400 的那些) 时应多加小心. 例如, 如果监听函数不会快速结束, 那么对消息的响应时间可能比系统预期的要长, 这可能会导致一些副作用. 如果监听函数返回一个整数来抑制对消息的进一步处理, 但系统期望不同的处理或响应方式时, 可能会发生不想要的行为.

当脚本显示系统对话框时(例如 MsgBox), 则不会监听到任何投递(Post) 到控件的消息. 例如, 如果脚本正显示消息框而用户点击一个 GUI 窗口上的按钮, 则 WM_LBUTTONDOWN 消息会被直接发送到按钮而不会调用监听函数.

尽管外部程序可以使用 PostThreadMessage() 或其他 API 调用直接投递(Post) 消息给脚本的线程, 但不建议这么做, 因为如果此时脚本正显示系统窗口(例如 消息框) . 则消息会丢失. 相反, 通常最好投递或发送(Send) 消息到脚本的主窗口或其中的某个 GUI 窗口.

CallbackCreate, OnExit, OnClipboardChange, PostMessage, SendMessage, 函数, 窗口消息列表, 线程, Critical, DllCall

示例

监听在 GUI 窗口中的鼠标点击. 相关主题: ContextMenu 事件

MyGui := Gui(, "Example Window")
MyGui.Add("Text",, "Click anywhere in this window.")
MyGui.Add("Edit", "w200")
MyGui.Show
OnMessage 0x0201, WM_LBUTTONDOWN

WM_LBUTTONDOWN(wParam, lParam, msg, hwnd)
{
    X := lParam & 0xFFFF
    Y := lParam >> 16
    Control := ""
    thisGui := GuiFromHwnd(hwnd)
    thisGuiControl := GuiCtrlFromHwnd(hwnd)
    if thisGuiControl
    {
        thisGui := thisGuiControl.Gui
        Control := "`n(in control " . thisGuiControl.ClassNN . ")"
    }
    ToolTip "You left-clicked in Gui window '" thisGui.Title "' at client coordinates " X "x" Y "." Control
}

检测系统的关机/注销动作并允许您中止它. 在 Windows Vista 及更高的版本中, 系统会显示一个用户界面, 显示哪个程序正在阻止关机/注销, 并允许用户强制关机/注销. 在较旧的操作系统上, 脚本显示一个确认提示. 相关主题: OnExit

; 下面的 DllCall 是可选的: 它告诉操作系统首先要关闭此脚本(在其他所有程序之前).
DllCall("kernel32.dll\SetProcessShutdownParameters", "UInt", 0x4FF, "UInt", 0)
OnMessage(0x0011, On_WM_QUERYENDSESSION)
Persistent

On_WM_QUERYENDSESSION(wParam, lParam, *)
{
    ENDSESSION_LOGOFF := 0x80000000
    if (lParam & ENDSESSION_LOGOFF)  ; 用户正在注销.
        EventType := "Logoff"
    else  ; 系统正在关机或重启.
        EventType := "Shutdown"
    try
    {
        ; 设置显示操作系统关闭 UI 的提示. 我们不会显示自己的确认提示
        ; 因为我们只有 5 秒钟的时间, 操作系统会显示关机 UI
        ; 同样, 没有可见窗口的程序在没有提供原因的情况下无法阻止关机.
        BlockShutdown("Example script attempting to prevent " EventType ".")
        return false
    }
    catch
    {
        ; ShutdownBlockReasonCreate 不可用,
        ; 所以这可能是 Windows XP, 2003 或 2000, 我们实际上可以防止关机.
        Result := MsgBox(EventType " in progress. Allow it?",, "YN")
        if (Result = "Yes")
            return true  ; 通知操作系统允许关机/注销操作继续.
        else
            return false  ; 通知操作系统中止关机/注销操作.
    }
}

BlockShutdown(Reason)
{
    ; 如果您的脚本具有可见的 GUI, 请使用它代替 A_ScriptHwnd.
    DllCall("ShutdownBlockReasonCreate", "ptr", A_ScriptHwnd, "wstr", Reason)
    OnExit StopBlockingShutdown
}

StopBlockingShutdown(*)
{
    OnExit StopBlockingShutdown, 0
    DllCall("ShutdownBlockReasonDestroy", "ptr", A_ScriptHwnd)
}

接收其他脚本或程序的自定义消息和最多两个数字(要发送字符串而不是数字, 请参阅下一个示例).

OnMessage 0x5555, MsgMonitor
OnMessage 0x5556, MsgMonitor
Persistent

MsgMonitor(wParam, lParam, msg, *)
{
    ; 由于尽快返回常常很重要, 所以最好使用 ToolTip 而不是
    ; 类似 MsgBox 的进行显示, 以避免阻止函数结束:
    ToolTip "Message " msg " arrived:`nWPARAM: " wParam "`nLPARAM: " lParam
}

; 下面的代码可用于其他脚本内来激发运行上面脚本中的函数:
SetTitleMatchMode 2
DetectHiddenWindows True
if WinExist("Name of Receiving Script.ahk ahk_class AutoHotkey")
    PostMessage 0x5555, 11, 22  ; 因为上面的 WinExist, 所以消息被发送到 "上次找到的窗口".
DetectHiddenWindows False  ; 必须在 PostMessage 之后才能关闭.

从一个脚本发送任意长度的字符串到另一个脚本. 要使用它, 请保存并运行下面的两个脚本, 然后按下 Win+Space 来显示输入框来让您输入字符串. 两个脚本必须使用相同的原生编码.

保存下面的脚本为 Receiver.ahk, 然后运行它:

#SingleInstance
OnMessage 0x004A, Receive_WM_COPYDATA  ; 0x004A is WM_COPYDATA
Persistent

Receive_WM_COPYDATA(wParam, lParam, msg, hwnd)
{
    StringAddress := NumGet(lParam, 2*A_PtrSize, "Ptr")  ; 检索 CopyDataStruct 的 lpData 成员.
    CopyOfData := StrGet(StringAddress)  ; 从结构中复制字符串.
    ; 比起 MsgBox, 应该用 ToolTip 显示, 这样我们可以及时返回:
    ToolTip A_ScriptName "`nReceived the following string:`n" CopyOfData
    return true  ;  返回 1(true) 是回复此消息的传统方式.
}

保存下面的脚本为 Sender.ahk, 接着运行它. 然后, 按下 Win+Space 热键.

TargetScriptTitle := "Receiver.ahk ahk_class AutoHotkey"

#space::  ; Win+Space 热键. 按下此热键会显示输入框用于输入消息字符串.
{
    ib := InputBox("Enter some text to Send:", "Send text via WM_COPYDATA")
    if ib.Result = "Cancel"  ; 用户按下了取消按钮.
        return
    result := Send_WM_COPYDATA(ib.Value, TargetScriptTitle)
    if result = ""
        MsgBox "SendMessage failed or timed out. Does the following WinTitle exist?:`n" TargetScriptTitle
    else if (result = 0)
        MsgBox "Message sent but the target window responded with 0, which may mean it ignored it."
}

Send_WM_COPYDATA(StringToSend, TargetScriptTitle)
; 此函数发送指定的字符串到指定的窗口然后返回收到的回复.
; 如果目标窗口处理了消息则回复为 1, 而消息被忽略了则为 0.
{
    CopyDataStruct := Buffer(3*A_PtrSize)  ; 分配结构的内存区域.
    ; 首先设置结构的 cbData 成员为字符串的大小, 包括它的零终止符:
    SizeInBytes := (StrLen(StringToSend) + 1) * 2
    NumPut( "Ptr", SizeInBytes  ; 操作系统要求这个需要完成.
          , "Ptr", StrPtr(StringToSend)  ; 设置 lpData 为到字符串自身的指针.
          , CopyDataStruct, A_PtrSize)
    Prev_DetectHiddenWindows := A_DetectHiddenWindows
    Prev_TitleMatchMode := A_TitleMatchMode
    DetectHiddenWindows True
    SetTitleMatchMode 2
    TimeOutTime := 4000  ; 可选的. 等待 receiver.ahk 响应的毫秒数. 默认是 5000
    ; 必须使用发送 SendMessage 而不是投递 PostMessage.
    RetValue := SendMessage(0x4a, 0, CopyDataStruct,, TargetScriptTitle,,,, TimeOutTime) ; 0x4a 是 WM_COPYDATA.
    DetectHiddenWindows Prev_DetectHiddenWindows  ; 恢复调用者原来的设置.
    SetTitleMatchMode Prev_TitleMatchMode         ; 同样.
    return RetValue  ; 返回 SendMessage 的回复给我们的调用者.
}

有关如何使用 OnMessage 来接收网络连接上数据到达时的通知的演示, 请参阅 WinLIRC 客户端脚本.

unixetc