Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

ControlClick fails to perform a double click in the Pos mode


  • Please log in to reply
21 replies to this topic
YMP
  • Members
  • 424 posts
  • Last active: Apr 05 2012 01:18 AM
  • Joined: 23 Dec 2006
For example, try to open a file or folder in Explorer's window, using co-ordinates. The corresponding row is highlighted, but what happens next depends on where the co-ordinates point at. If they point at the file's icon, nothing happens. If they point at the file's name, it is switched to the edit mode (placed into a rectangle with a text cursor).
So it looks like the window receives not a double click, but two single clicks.
The code I used was like this:
  ControlClick, x297 y105, ahk_class ExploreWClass,, Left, 2
I also tried placing SetControlDelay, -1 before this command, but that didn't help.

PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005
I don't see how this is a bug, the manual page doesn't have the word "double" in it...
AutoHotkey cannot fail to do something it doesn't promise...

ClickCount | The number of clicks to send, which can be an expression. If omitted or blank, 1 click is sent.

And I guess it also depends on your settings, since you can change the maximum interval between two clicks (in the Mouse Settings) of a double click.
Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
I suspect what's happening is that the application expects to receive the WM_LBUTTONDBLCLK message for a double-click, but AutoHotkey only sends a two sets of WM_LBUTTONDOWN/UP messages instead. I seem to remember that the OS automatically generates WM_LBUTTONDBLCLK when a short enough time has passed between two single clicks. However, it probably doesn't do this when AutoHotkey sends the messages directly to the control.

Although this could be considered a bug, changing the current behavior might break existing scripts. On the other hand, usages of double-click are probably rare, so maybe the added flexibility is worth the slight risk of breaking some scripts.

Thanks for reporting it.

YMP
  • Members
  • 424 posts
  • Last active: Apr 05 2012 01:18 AM
  • Joined: 23 Dec 2006
Yes, this seems to be the case. I have tracked that out already, using a test script with a Gui window and OnMessage functions. When I specified 2 for ClickCount in the MouseClick command, the window received:

WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDBLCLK
WM_LBUTTONUP

But if I did the same in ControlClick, there was this series instead:

WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDOWN
WM_LBUTTONUP

But, after I found that out, I thougt that probably PhiLho was right and that was how it had been intended to work, perhaps due to some side effects of double-clicking in these circumstances, or something else.
Thank you, Chris, for the clarification!

PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005
Well, your expectation is somehow understandable, but as Chris stated, it is quite rare to have to double-click a control.
Otherwise, maybe Chris could change the ClickCount parameter of ControlClick, MouseClick, Click (and others?) to accept a "D" parameter to perform a true double-click (ie. sending the appropriate messages in sequence).
Or you can do it yourself with SendMessage...
Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

YMP
  • Members
  • 424 posts
  • Last active: Apr 05 2012 01:18 AM
  • Joined: 23 Dec 2006
I by no means insist on changing anything if it's not reasonable. As for my expectation, I just think it's natural for many other people as well. You automatically expect 2 clicks to mean a double click unless stated otherwise. A kind of prejudice.
I have tried to achieve this through SendMessage, but it looks like in this case you can't just send the messages to the parent window of the control (at least it doesn't work in Explorer), but should aim at the control itself. For that, you have to know which control is at the specified coords. I guess ControlClick somehow obtains this information prior to sending mouse messages. I still don't know how to do that. I haven't done any deep research on that, though. Maybe there's a way except MouseGetPos.
For example, WinGet to get a list of the window's controls, then ControlGetPos for each of them to see if the coords lie inside it. Something like that. Also, the coords have to be transformed into control-relative.

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
Thanks for the informative remarks. I'll try to add an option or mode to ControlClick that sends the series of events that corresponds to a double-click.

ashishpok
  • Members
  • 9 posts
  • Last active: Oct 23 2007 01:55 AM
  • Joined: 19 Sep 2007
Hi Chris,
I was looking for something like this too. Implementing Double-Click without having to move the mouse to (X,Y) coordinate, using MouseClick or Click commands. I cant seem to figure out if there is a way to do it using ControlClick. It seems to implement 2 single clicks.

engunneer
  • Moderators
  • 9162 posts
  • Last active: Sep 12 2014 10:36 PM
  • Joined: 30 Aug 2005
ControlClick has a ClickCount parameter

ashishpok
  • Members
  • 9 posts
  • Last active: Oct 23 2007 01:55 AM
  • Joined: 19 Sep 2007
Yes, But it doesnt implement Double-Click. As you can read on this post, it only sends two clicks without the Windows Double Click message. In effect, it is like sending two single clicks.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
You can manually send WM_LBUTTONDBLCLICK and WM_LBUTTONUP messages to the control. Seems to work in my C++.net test app, but not the Mouse Properties dialog...

For that, you have to know which control is at the specified coords. I guess ControlClick somehow obtains this information prior to sending mouse messages. I still don't know how to do that.

ControlClick uses EnumChildWindows. Here's the result of porting it to AutoHotkey (script :roll:):
; Retrieves the control at the specified point.
; X         [in]    X-coordinate relative to the top-left of the window.
; Y         [in]    Y-coordinate relative to the top-left of the window.
; WinTitle  [in]    Title of the window whose controls will be searched.
; WinText   [in]
; cX        [out]   X-coordinate relative to the top-left of the control.
; cY        [out]   Y-coordinate relative to the top-left of the control.
; ExcludeTitle [in]
; ExcludeText  [in]
; Return Value:     The hwnd of the control if found, otherwise the hwnd of the window.
ControlFromPoint(X, Y, WinTitle="", WinText="", ByRef cX="", ByRef cY="", ExcludeTitle="", ExcludeText="")
{
    static EnumChildFindPointProc=0
    if !EnumChildFindPointProc
        EnumChildFindPointProc := RegisterCallback("EnumChildFindPoint","Fast")
    
    if !(target_window := WinExist(WinTitle, WinText, ExcludeTitle, ExcludeText))
        return false
    
    VarSetCapacity(rect, 16)
    DllCall("GetWindowRect","uint",target_window,"uint",&rect)
    VarSetCapacity(pah, 36, 0)
    NumPut(X + NumGet(rect,0,"int"), pah,0,"int")
    NumPut(Y + NumGet(rect,4,"int"), pah,4,"int")
    DllCall("EnumChildWindows","uint",target_window,"uint",EnumChildFindPointProc,"uint",&pah)
    control_window := NumGet(pah,24) ? NumGet(pah,24) : target_window
    DllCall("ScreenToClient","uint",control_window,"uint",&pah)
    cX:=NumGet(pah,0,"int"), cY:=NumGet(pah,4,"int")
    return control_window
}

; Ported from AutoHotkey::script2.cpp::EnumChildFindPoint()
EnumChildFindPoint(aWnd, lParam)
{
    if !DllCall("IsWindowVisible","uint",aWnd)
        return true
    VarSetCapacity(rect, 16)
    if !DllCall("GetWindowRect","uint",aWnd,"uint",&rect)
        return true
    pt_x:=NumGet(lParam+0,0,"int"), pt_y:=NumGet(lParam+0,4,"int")
    rect_left:=NumGet(rect,0,"int"), rect_right:=NumGet(rect,8,"int")
    rect_top:=NumGet(rect,4,"int"), rect_bottom:=NumGet(rect,12,"int")
    if (pt_x >= rect_left && pt_x <= rect_right && pt_y >= rect_top && pt_y <= rect_bottom)
    {
        center_x := rect_left + (rect_right - rect_left) / 2
        center_y := rect_top + (rect_bottom - rect_top) / 2
        distance := Sqrt((pt_x-center_x)**2 + (pt_y-center_y)**2)
        update_it := !NumGet(lParam+24)
        if (!update_it)
        {
            rect_found_left:=NumGet(lParam+8,0,"int"), rect_found_right:=NumGet(lParam+8,8,"int")
            rect_found_top:=NumGet(lParam+8,4,"int"), rect_found_bottom:=NumGet(lParam+8,12,"int")
            if (rect_left >= rect_found_left && rect_right <= rect_found_right
                && rect_top >= rect_found_top && rect_bottom <= rect_found_bottom)
                update_it := true
            else if (distance < NumGet(lParam+28,0,"double")
                && (rect_found_left < rect_left || rect_found_right > rect_right
                 || rect_found_top < rect_top || rect_found_bottom > rect_bottom))
                 update_it := true
        }
        if (update_it)
        {
            NumPut(aWnd, lParam+24)
            DllCall("RtlMoveMemory","uint",lParam+8,"uint",&rect,"uint",16)
            NumPut(distance, lParam+28, 0, "double")
        }
    }
    return true
}
Usage example (pointless because it uses the mouse position, but you get the idea):
^LButton::
    CoordMode,Mouse,Screen
    MouseGetPos, X, Y, win
    WinGetPos, wX, wY,,, ahk_id %win%
    hwnd := ControlFromPoint(X-wX, Y-wY, "ahk_id " win,"", X, Y)
    
    wParam := 0x1 ; 0x1=MK_LBUTTON
    ;    | (GetKeyState("Shift")*0x4)|(GetKeyState("Ctrl")*0x8)
    ; WM_LBUTTONDBLCLICK=0x203
    SendMessage, 0x203, wParam, (x & 0xFFFF) | ((y & 0xFFFF)<<16),, ahk_id %hwnd%
    wParam &= ~0x1
    ; WM_LBUTTONUP=0x203
    SendMessage, 0x202, wParam, (x & 0xFFFF) | ((y & 0xFFFF)<<16),, ahk_id %hwnd%
return


YMP
  • Members
  • 424 posts
  • Last active: Apr 05 2012 01:18 AM
  • Joined: 23 Dec 2006
Great work, lexikos. :D Thanks!
But, to be honest, I don't fully understand your sample script, in particular the role of MK_LBUTTON. Was that code intended to produce a whole double click or only its second half? However, when I tested it in Explorer, it opened a folder or file only if they had been previously highlighted.

Eventually I had to replace SendMessage with PostMessage because, if the mouse cursor was out of the Explorer's window, SendMessage resulted only in highlighting. Also, another strange thing, wav and mp3 files would not open, God knows why. PostMessage solved that too, God knows how.

Here's so far my wrapper to your functions.
ControlClick2(X, Y, WinTitle="", WinText="", ExcludeTitle="", ExcludeText="")
{
  hwnd:=ControlFromPoint(X, Y, WinTitle, WinText, cX, cY
                             , ExcludeTitle, ExcludeText)
  PostMessage, 0x201, 0, cX&0xFFFF | cY<<16,, ahk_id %hwnd% ; WM_LBUTTONDOWN
  PostMessage, 0x202, 0, cX&0xFFFF | cY<<16,, ahk_id %hwnd% ; WM_LBUTTONUP
  PostMessage, 0x203, 0, cX&0xFFFF | cY<<16,, ahk_id %hwnd% ; WM_LBUTTONDBLCLCK
  PostMessage, 0x202, 0, cX&0xFFFF | cY<<16,, ahk_id %hwnd% ; WM_LBUTTONUP
}
By the way, from my, some time ago, experiments with two monitors, (y & 0xFFFF) seems redundant. Even when I placed the second monitor above the first, making its Y coordinates negative, the simpler expression y<<16 worked well for its windows. Am I wrong? ;)

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

But, to be honest, I don't fully understand your sample script, in particular the role of MK_LBUTTON.

It's there in case the app checks for it. Logically, there'd be no need since LButton is obviously down when WM_LBUTTONDBLCLICK happens. Having the MK_LBUTTON flag set is the documented behaviour.

Was that code intended to produce a whole double click or only its second half?

Only the second half. I see you figured out how to do the first half (ControlClick also would've worked, but it would probably be less efficient since it'd also enumerate the child windows.)

Eventually I had to replace SendMessage with PostMessage

Strange that it made a difference. I think ControlClick uses PostMessage, actually.

By the way, from my, some time ago, experiments with two monitors, (y & 0xFFFF) seems redundant.

Yes, it is redundant. The bits 0xFFFF0000 are shifted to 0xFFFF00000000, which are outside the range of a 32-bit integer (so they are "lost" when AutoHotkey passes them to SendMessageTimeout().)


Btw, I actually wrote/ported most of that to implement ControlMouseMove. I only did about half a minute of testing on the double-click part.

ashishpok
  • Members
  • 9 posts
  • Last active: Oct 23 2007 01:55 AM
  • Joined: 19 Sep 2007
Thanks Everyone! Works as charm :)

bigboy
  • Members
  • 6 posts
  • Last active: Dec 14 2008 05:36 AM
  • Joined: 10 Sep 2008
Ok, hopefully this is the solution to double click in a listview control once it has been highlighted. Question is how do I put the function and the wrapper so that my script can access it???? and what is the syntax for controlclick2?? Is it controlclick2, ,,, or controlclick2() or what?? Please give details!
Thanks!
bigboy