OnMessage() [v1.0.38+]

Specifies a function to run automatically when the script receives the specified message.

OnMessage(MsgNumber [, "FunctionName"])

 

Parameters

MsgNumber The number of the message to monitor or query, which should be between 0 and 4294967295 (0xFFFFFFFF). If you do not wish to monitor a system message (that is, one below 0x400), it is best to choose a number greater than 4096 (0x1000) to the extent you have a choice. This reduces the chance of interfering with messages used internally by current and future versions of AutoHotkey.
FunctionName The name of the function to be called automatically when the script receives MsgNumber. Omit this parameter to retrieve the name of the function currently monitoring MsgNumber (blank if none). Specify an empty string ("") or an empty variable to turn off the monitoring of MsgNumber.

 

Return Values

If FunctionName is omitted, it returns the name of the function currently monitoring MsgNumber (blank if none). However, no changes are made.

If FunctionName is explicitly blank (e.g. ""), it returns the name of the function currently monitoring MsgNumber (blank if none), then turns off the monitoring of MsgNumber.

If FunctionName is non-blank: If MsgNumber is already being monitored, it returns the name of that function then puts the new function into effect. Otherwise, it assigns FunctionName to monitor MsgNumber then returns the same FunctionName. In either case, a blank value is returned upon failure. Failure occurs when FunctionName: 1) does not exist (perhaps due to missing quotes around FunctionName); 2) accepts more than four parameters; or 3) has any ByRef or optional parameters. It will also fail if the script attempts to monitor a new message when there are already 500 messages being monitored.

 

Remarks

A function assigned to monitor one or more messages can accept up to four parameters:

MyMessageMonitor(wParam, lParam, msg, hwnd)
{
	... body of function...
}

Although the names you give the parameters do not matter, the following information is sequentially assigned to them:

Parameter #1: The message's WPARAM value, which is an integer between 0 and 4294967295.
Parameter #2: The message's LPARAM value, which is an integer between 0 and 4294967295.
Parameter #3: The message number, which is useful in cases where a function monitors more than one message.
Parameter #4: The handle (HWND) of the window or control to which the message was sent.

You can omit one or more parameters from the end of the list if the corresponding information is not needed. For example, a function defined as MyMsgMonitor(wParam, lParam) would receive only the first two parameters, and one defined as MyMsgMonitor() would receive none of them.

If the monitor function uses Return without any parameters, specifies a blank value such as "", or it never uses Return at all, the incoming message goes on to be processed normally when the function finishes. The same thing happens if the function Exits or causes a runtime error such as running a nonexistent file. By contrast, returning an integer between -2147483648 and 4294967295 causes that number to be sent immediately as a reply; that is, the program does not process the message any further. For example, a function monitoring WM_LBUTTONDOWN (0x201) may return an integer to prevent the target window from receiving the message. In many cases (such as a message arriving via PostMessage), it does not matter which integer is returned; but if in doubt, 0 is usually safest.

In addition to the parameters received above, a function called via message may also check the values of the following built-in variables:

When called via message, a function's last found window starts off as the parent window to which the message was sent (even if it was sent to a control). If the window is hidden but not a GUI window (such as the script's main window), turn on DetectHiddenWindows before using it. For example:

DetectHiddenWindows On
MsgParentWindow := WinExist()  ; This stores the unique ID of the window to which the message was sent.

If an incoming WPARAM or LPARAM is intended to be a signed value, any negative numbers can be revealed by following this example:

if wParam > 0x7FFFFFFF
	wParam := -(~wParam) - 1

Unlike a normal function-call, the arrival of a monitored message calls the function as a new thread. Because of this, the function starts off fresh with the default values for settings such as SetKeyDelay and DetectHiddenWindows. These defaults can be changed in the auto-execute section.

Any script that calls OnMessage anywhere is automatically persistent. It is also single-instance unless #SingleInstance has been used to override that.

If a message arrives while its function is still running due to a previous arrival of the same message, the function will not be called again; instead, the message will be treated as unmonitored. If this is undesirable, a message greater than or equal to 0x312 can be buffered until its function completes by specifying Critical as the first line of the function. Alternatively, Thread Interrupt can achieve the same thing as long as it lasts long enough for the function to finish. By contrast, a message less than 0x312 cannot be buffered by Critical or Thread Interrupt; the only way to guarantee that no such messages are missed is to ensure the function finishes in under 10 milliseconds. One way to do this is to have it queue up a future thread by posting to its own script a monitored message number higher than 0x312. That message's function should use Critical as its first line to ensure that its messages are buffered.

If a monitored message that is numerically less than 0x312 arrives while the script is absolutely uninterruptible -- such as while a menu is displayed, a KeyDelay/MouseDelay is in progress, or the clipboard is being opened -- the message's function will not be called and the message will be treated as unmonitored. By contrast, a monitored message of 0x312 or higher will be buffered during these uninterruptible periods; that is, its function will be called when the script becomes interruptible.

If a monitored message numerically less than 0x312 arrives while the script is uninterruptible merely due to the settings of Thread Interrupt or Critical, the current thread will be interrupted so that the function can be called. By contrast, a monitored message of 0x312 or higher will be buffered until the thread finishes or becomes interruptible.

The priority of OnMessage threads is always 0. Consequently, no messages are monitored or buffered when the current thread's priority is higher than 0.

Caution should be used when monitoring system messages (those below 0x400). For example, if a monitor function does not finish quickly, the response to the message might take longer than the system expects, which might cause side-effects. Unwanted behavior may also occur if a monitor function returns an integer to suppress further processing of a message, but the system expected different processing or a different response.

When the script is displaying a system dialog such as MsgBox, any message posted or sent to a control is not monitored. For example, if the script is displaying a MsgBox and the user clicks a button in a GUI window, the WM_LBUTTONDOWN message is sent directly to the button without calling the monitor function.

Although an external program may post messages directly to a script's thread via PostThreadMessage() or other API call, this is not recommended because the messages would be lost if the script is displaying a system window such as a MsgBox. Instead, it is usually best to post or send the messages to the script's main window or one of its GUI windows.

 

Related

OnExit, OnClipboardChange, Post/SendMessage, Functions, List of Windows Messages, Threads, Critical, DllCall()

 

Examples


; Example: The following is a working script that monitors mouse clicks in a GUI window. ; Related topic: GuiContextMenu Gui, Add, Text,, Click anywhere in this window. Gui, Add, Edit, w200 vMyEdit Gui, Show OnMessage(0x201, "WM_LBUTTONDOWN") return WM_LBUTTONDOWN(wParam, lParam) { X := lParam & 0xFFFF Y := lParam >> 16 if A_GuiControl Control := "`n(in control " . A_GuiControl . ")" ToolTip You left-clicked in Gui window #%A_Gui% at client coordinates %X%x%Y%.%Control% } GuiClose: ExitApp
; Example: The following script detects system shutdown/logoff and allows you to abort it. ; Related topic: OnExit OnMessage(0x11, "WM_QUERYENDSESSION") return WM_QUERYENDSESSION(wParam, lParam) { ENDSESSION_LOGOFF = 0x80000000 if (lParam & ENDSESSION_LOGOFF) ; User is logging off. EventType = Logoff else ; System is either shutting down or restarting. EventType = Shutdown MsgBox, 4,, %EventType% in progress. Allow it? IfMsgBox Yes return true ; Tell the OS to allow the shutdown/logoff to continue. else return false ; Tell the OS to abort the shutdown/logoff. }
; Example: Have a script receive a custom message and up to two numbers from some other script or program ; (to send strings rather than numbers, see the example after this one). OnMessage(0x5555, "MsgMonitor") OnMessage(0x5556, "MsgMonitor") MsgMonitor(wParam, lParam, msg) { ; Since returning quickly is often important, it is better to use a ToolTip than ; something like MsgBox that would prevent the function from finishing: ToolTip Message %msg% arrived:`nWPARAM: %wParam%`nLPARAM: %lParam% } ; The following could be used inside some other script to run the function inside the above script: SetTitleMatchMode 2 DetectHiddenWindows On if WinExist("Name of Receiving Script.ahk ahk_class AutoHotkey") PostMessage, 0x5555, 11, 22 ; The message is sent to the "last found window" due to WinExist() above. DetectHiddenWindows Off ; Must not be turned off until after PostMessage.
; Example: Send a string of any length from one script to another. This is a working example. ; To use it, save and run both of the following scripts then press Win+Space to show an ; InputBox that will prompt you to type in a string. ; Save the following script as "Receiver.ahk" then launch it: OnMessage(0x4a, "Receive_WM_COPYDATA") ; 0x4a is WM_COPYDATA return Receive_WM_COPYDATA(wParam, lParam) { lpDataAddress := lParam + 8 ; This is the address of CopyDataStruct's lpData member. lpData := 0 ; Init prior to accumulation in the loop. Loop 4 ; For each byte in the lpData integer { lpData := lpData | (*lpDataAddress << 8 * (A_Index - 1)) ; Build the integer from its bytes. lpDataAddress += 1 ; Move on to the next byte. } ; lpData contains the address of the string to be copied (must be a zero-terminated string). DataLength := DllCall("lstrlen", UInt, lpData) if DataLength <= 0 ToolTip %A_ScriptName%`nA blank string was received or there was an error. else { VarSetCapacity(CopyOfData, DataLength) DllCall("lstrcpy", str, CopyOfData, UInt, lpData) ; Copy the string out of the structure. ; Show it with ToolTip vs. MsgBox so we can return in a timely fashion: ToolTip %A_ScriptName%`nReceived the following string:`n%CopyOfData% } return true ; Returning 1 (true) is the traditional way to acknowledge this message. } ; Save the following script as "Sender.ahk" then launch it. After that, press the Win+Space hotkey. TargetScriptTitle = Receiver.ahk ahk_class AutoHotkey #space:: ; Win+Space hotkey. Press it to show an InputBox for entry of a message string. InputBox, StringToSend, Send text via WM_COPYDATA, Enter some text to Send: if ErrorLevel ; User pressed the Cancel button. return result := Send_WM_COPYDATA(StringToSend, TargetScriptTitle) if result = FAIL MsgBox SendMessage failed. 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. return Send_WM_COPYDATA(ByRef StringToSend, ByRef TargetScriptTitle) ; ByRef saves a little memory in this case. ; This function sends the specified string to the specified window and returns the reply. ; The reply will be 1 if the target window processed the message or 0 if it ignored it. { VarSetCapacity(CopyDataStruct, 12, 0) ; Set up the structure's memory area. ; First set the structure's cbData member to the size of the string, including its zero terminator: InsertInteger(StrLen(StringToSend) + 1, CopyDataStruct, 4) ; OS requires that this be done. InsertInteger(&StringToSend, CopyDataStruct, 8) ; Set lpData to point to the string itself. Prev_DetectHiddenWindows := A_DetectHiddenWindows Prev_TitleMatchMode := A_TitleMatchMode DetectHiddenWindows On SetTitleMatchMode 2 SendMessage, 0x4a, 0, &CopyDataStruct,, %TargetScriptTitle% ; 0x4a is WM_COPYDATA. Must use Send not Post. DetectHiddenWindows %Prev_DetectHiddenWindows% ; Restore original setting for the caller. SetTitleMatchMode %Prev_TitleMatchMode% ; Same. return ErrorLevel ; Return SendMessage's reply back to our caller. } InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4) { Loop %pSize% ; Copy each byte in the integer into the structure as raw binary data. DllCall("RtlFillMemory", UInt, &pDest + pOffset + A_Index-1, UInt, 1, UChar, pInteger >> 8*(A_Index-1) & 0xFF) }
; Example: See the WinLIRC client script for a demonstration of how to use OnMessage() to receive ; notification when data has arrived on a network connection.