OnMessage: Not working in Keysharp or syntax unknown.

Issue #34 closed
Winter Laite created an issue

Working AHK code, modified from non-working example on docs page:

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 ". `nHwnd is " MyGui.Hwnd)
}
SetWorkingDir(A_ScriptDir)

In Keysharp, OnMessage() apparently takes (string number, string function, string maxThreads). This requires guessing as to what to put for maxThreads, but I chose “1”. The code below throws no errors, but the function WM_LBUTTTONDOWN is not called.

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

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 ". `nHwnd is " MyGui.Hwnd)
}
SetWorkingDir(A_ScriptDir)

I have also tried the following code without success, to make sure that nothing going on inside the function was causing the error.

WM_LBUTTONDOWN(wParam, lParam, msg, hwnd)
{
    MsgBox("Message Received")
}
SetWorkingDir(A_ScriptDir)

Comments (14)

  1. Winter Laite reporter

    Note: In Keysharp OnMessage takes three strings:

            public static void OnMessage(string number, string function, string maxThreads)
            {
            }
    

    But two of those parameters will never be strings. Intentional? At any rate, it is not working at the moment.

  2. Matt Feemster repo owner

    I’ve made an initial commit which implements this. There are likely bugs and missing functionality. But the sample code with button down works in my testing.

    A few notes/caveats:

    The functionality with threading mentioned in the AHK documentation is not supported. The handlers are just called inline.

    That means the third parameter only specifies where the handler is added using the usual handler rules (-1, 0, 1), not anything to do with threading.

    I am not exactly sure how we’d do this, since we’re using real threads. It makes way more sense to me to just call the handlers inline. I am tempted to just leave it unless someone complains. It seems like a very obscure area of functionality. I am hoping threaded calls of custom message handlers is not something people really depend on.

  3. Winter Laite reporter

    Continued testing with some success following commit, some issues remain.

    This example works.

    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)
    }
    

    To my surprise, it also works when the message number is passed as a string, i.e. 'OnMessage(“0x0201”, “WM_LBUTTONDOWN”)

    I haven’t been able to successfully test other examples due to a (possible) bug with DetectHiddenWindows.

  4. Matt Feemster repo owner

    So what is the status of this? Are we considering it fixed? If not, please specify what still doesn’t work and provide an example script to test.

  5. Winter Laite reporter

    Not fixed. Send/Receive scripts below, taken basically from Ex. 3 on the docs page for OnMessage().

    Receiver script:

    OnMessage(0x0555, "MsgMonitor")
    OnMessage(0x0556, "MsgMonitor")
    Persistent(True)
    
    MyGui := Gui()
    MyGui.Add("Text", "w200 h50", "Placeholder to make not hidden.")
    OSDText := MyGui.Add("Text", "w200 h50 x10 y+10", "You can be replaced!")
    MyGui.Show()
    
    MsgMonitor(wParam, lParam, msg, *)
    {
        ; Since returning quickly is often important, it is better to use ToolTip than
        ; something like MsgBox that would prevent the function from finishing:
        ToolTip("Message " msg " arrived:`nWPARAM: " wParam "`nLPARAM: " lParam)
        ;ControlSetText("Got " msg, "The treasure map")
        OSDText.Text := "Got message " msg
    }
    

    Sender script.

    DetectHiddenWindows(True)
    
    ^!]::
    {
    DetectHiddenWindows(True)
        if (WinExist("Receiver2KS.ahk")) {
            MsgBox("It exists")
            PostMessage("0xFF55", 11, 22)  ; The message is sent  to the "last found window" due to WinExist above.
            DetectHiddenWindows(False)  ; Must not be turned off until after PostMessage.
        }
        else {
            MsgBox("Darn, I can't find the window to send a message to!")
        }
    }
    

    First run receiver, then sender. Trigger sending with the hotkey shows that the receiver exists, but no tooltip displays.

  6. Winter Laite reporter

    Larger and completely non-working send/receive w/OnMessage(). As we have discussed on Discord, the function called by receipt of OnMessage() takes 4 params, wparam, lparam, msg, hwnd, all of which can be omitted in the proper fashion with '*'. WPARAM and LPARAM are unsigned 32-bit integers. Keysharp does not have StrPtr to provide an address for a string, so the examples are both AHK.

    Receiver:

    #SingleInstance
    OnMessage(0x004A, "Receive_WM_COPYDATA")  ; 0x004A is WM_COPYDATA
    Persistent(True)
    
    Receive_WM_COPYDATA(wParam, lParam, msg, hwnd)
    {
        StringAddress := NumGet(lParam, 2*A_PtrSize, "Ptr")  ; Retrieves the CopyDataStruct's lpData member.
        CopyOfData := StrGet(StringAddress)  ; 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.
    }
    

    (Here I obviously can’t get a string address. Passing the string won’t work, wparam and lparam have to be integers.)

    Sender:

    TargetScriptTitle := "ReceiverKS.ahk ahk_class AutoHotkey"
    
    #space::  ; Win+Space hotkey. Press it to show an input box for entry of a message string.
    {
        ib := InputBox("Enter some text to Send:", "Send text via WM_COPYDATA")
        if (ib.Result = "Cancel")  ; User pressed the Cancel button.
            return
        result := Send_WM_COPYDATA(ib.Value, TargetScriptTitle)
        MsgBox("result is " result)
        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.")
        else
            MsgBox("Nothing happened!")
    }
    
    Send_WM_COPYDATA(StringToSend, TargetScriptTitle)
    ; This function sends the specified string to the specified window and returns the reply.
    ; The reply is 1 if the target window processed the message, or 0 if it ignored it.
    {
        CopyDataStruct := Buffer(3*A_PtrSize)  ; Set up the structure's memory area.
        ; First set the structure's cbData member to the size of the string, including its zero terminator:
        SizeInBytes := (StrLen(StringToSend) + 1) * 2
        NumPut( "Ptr", SizeInBytes  ; OS requires that this be done.
              , "Ptr", StrPtr(StringToSend)  ; Set lpData to point to the string itself.
              , CopyDataStruct, A_PtrSize)
        Prev_DetectHiddenWindows := A_DetectHiddenWindows
        Prev_TitleMatchMode := A_TitleMatchMode
        DetectHiddenWindows(True)
        SetTitleMatchMode 2
        TimeOutTime := 4000  ; Optional. Milliseconds to wait for response from receiver.ahk. Default is 5000
        ; Must use SendMessage not PostMessage.
        RetValue := SendMessage(0x004A, 0, CopyDataStruct,, TargetScriptTitle,,,, TimeOutTime) ; 0x004A is WM_COPYDATA.
        DetectHiddenWindows(Prev_DetectHiddenWindows)  ; Restore original setting for the caller.
        SetTitleMatchMode(Prev_TitleMatchMode)         ; Same.
        return RetValue  ; Return SendMessage's reply back to our caller.
    }
    

    I don’t know how to convert the CopyDataStruct assignment and the NumPut call into something that will pass a string.

  7. Winter Laite reporter

    This is not a GUI-only issue. OnMessage, PostMessage and SendMessage are often used when there is no AHK GUI created at all.

  8. Matt Feemster repo owner

    I’ve committed a fix, here are a few points.

    1. In PostMessage(), you are sending the value as a string. Send it as a number.

    2. Also, you are trying to send 0xFF55, but you are listening to 0x0555 and 0x556. If those are what you are listening to, then at least one of those is what you should be sending.

    To toggle between the two, try this function.

    b := true

    ^!]::
    {
    global b
    DetectHiddenWindows(True)
    if (WinExist("assign.ahk")) {
    MsgBox("It exists")
    PostMessage(b ? 0x0555 : 0x0556, 11, 22) ; The message is sent to the "last found window" due to WinExist above.
    DetectHiddenWindows(False) ; Must not be turned off until after PostMessage.
    b := !b
    }
    else {
    MsgBox("Darn, I can't find the window to send a message to!")
    }
    }

    3) This appears not to work when the receiving window is minimized. Please verify you are experiencing the same, then let’s decide how it should be working.

    4) I changed a very important function that is used when calling any events of methods on objects. So please do a good run through of your existing tests to make sure everything still works. My unit tests are still working fine on my end.

    5) The fix included severe bugs in PostMessage() and SendMessage() which should be good now.

  9. Winter Laite reporter

    Thanks very much. All the tests in guitest.ahk still work. Your code is a better test. For reference, here are what I have that work, as Sender2KS.ahk and Receiver2KS.ahk.

    Sender2KS.ahk:

    SetTitleMatchMode(2)
    DetectHiddenWindows(True)
    b := true
    
    ^!]::
    {
    global b
    DetectHiddenWindows(True)
        if (WinExist("Receiver2KS.ahk")) {
            PostMessage(b ? 0x0555 : 0x0556, 11, 22) ; The message is sent to the "last found window" due to WinExist above.
            DetectHiddenWindows(False) ; Must not be turned off until after PostMessage.
        b := !b
        }
        else {
        MsgBox("Darn, I can't find the window to send a message to!")
        }
    }
    

    Receiver2KS.ahk

    OnMessage(0x0555, "MsgMonitor")
    OnMessage(0x0556, "MsgMonitor")
    Persistent(True)
    
    MyGui := Gui()
    MyGui.Add("Text", "w200 h50", "Placeholder to make not hidden.")
    OSDText := MyGui.Add("Text", "w200 h50 x10 y+10", "You can be replaced!")
    MyGui.Show()
    
    MsgMonitor(wParam, lParam, msg, *)
    {
        ToolTip("Message: 0x" Format("{1:X}", msg) " arrived:`nWPARAM: " wParam "`nLPARAM: " lParam)
    }
    

    N.B: *As you indicated, this does not work when minimized, and the MsgBox for failure in SenderKS.ahk never shows.*

  10. Log in to comment