MoveTogether()

Post your working scripts, libraries and tools
DRocks
Posts: 518
Joined: 08 May 2018, 10:20

Re: MoveTogether()

18 Mar 2019, 14:57

Hello wolf

Thank you for providing this function.
It solved a problem I had no solution for until I find this. My problem was that when I use always-on-top Gui overlays to add-on a main Gui window, if you drag the main window the overlays will stay in their place and it looks really stupid :D

So yes this is a good solution thank you.

While implementing it in my script, discovered that if I dragged any other window related to my script, all the window in handles list would move even if I am not dragging any of them.
So I have added these two lines which solved this issue for me.

Edit: Disclaimer. I was using first post version and have not tested the most recent one yet (2019-03-19). Most recent is found on page 1 after a few posts.

Just wanted to let you know whiloe also saying thanks.

Code: Select all

;-----------------------------------------------------------------------------------------------------------------------------------------
MoveTogether(wParam, lParam, _, hWnd) { 			   ; using DeferWindowPos
;-----------------------------------------------------------------------------------------------------------------------------------------
	; https://www.autohotkey.com/boards/viewtopic.php?t=43192		by Wolf_II
	
	;static init := OnMessage(0x0A1, "MoveTogether") ; WM_NCLBUTTONDOWN
	global MoveTogetherHandles := [hGuiMain, hGuiGdipOverlay, hlv_Tx, hLB2_type] 
	
	
	; Here ---
	if (hWnd != MoveTogetherHandles[1]) ; MainWindow is assumed to be key #1 in Handles Object
		return ; Do not execute if we are dragging another window than the Main Window
	; --End
	
	
    ;---------------------------------------------
	CoordMode, Mouse, Screen    ; for MouseGetPos
	SetWinDelay, -1             ; for WinActivate
    ;---------------------------------------------
	
	IfNotEqual, wParam, 2, Return ; HTCAPTION (only part of title bar)
	M_old_X := lParam & 0xFFFF, M_old_Y := lParam >> 16 & 0xFFFF
	WinActivate, ahk_id %hWnd%
	
	For each, Handle in MoveTogetherHandles
		WinGetPos, W%A_Index%_old_X
                   , W%A_Index%_old_Y
                   , W%A_Index%_W
                   , W%A_Index%_H
                   , ahk_id %Handle%
	
	While GetKeyState("LButton") {
		MouseGetPos, M_new_X, M_new_Y
		
        ; get dX and dY from mouse, remember X and Y for next iteration
		dX := M_new_X - M_old_X, M_old_X := M_new_X
	   , dY := M_new_Y - M_old_Y, M_old_Y := M_new_Y
		
        ; calc new X and Y for W[i], updating old X and Y
		Loop, % MoveTogetherHandles.Length()
			W%A_Index%_new_X := W%A_Index%_old_X += dX
             , W%A_Index%_new_Y := W%A_Index%_old_Y += dY
		
        ; DeferWindowPos cycle
		hDWP := DllCall("BeginDeferWindowPos", "Int", MoveTogetherHandles.Length(), "Ptr")
		For each, Handle in MoveTogetherHandles
			hDWP := DllCall("DeferWindowPos", "Ptr", hDWP
					    , "Ptr", Handle, "Ptr", 0
					    , "Int", W%A_Index%_new_X
					    , "Int", W%A_Index%_new_Y
					    , "Int", W%A_Index%_W
					    , "Int", W%A_Index%_H
					    , "UInt", 0x0214, "Ptr")
		DllCall("EndDeferWindowPos", "Ptr", hDWP)
	}
}

Last edited by DRocks on 19 Mar 2019, 07:21, edited 2 times in total.
0x00
Posts: 80
Joined: 22 Jan 2019, 13:12

Re: MoveTogether()

19 Mar 2019, 00:05

Cool,I used majkinetor's DockA too, as of that last iteration, i like your version better. :thumbup:
wolf_II
Posts: 2688
Joined: 08 Feb 2015, 20:55

Re: MoveTogether()

19 Mar 2019, 01:52

@elmo:
@DRocks:
@0x00:

Thanks for the feedback. I am glad my contribution is useful. eeh, ... has been made useful thanks to all the collaborators.
:D
DRocks
Posts: 518
Joined: 08 May 2018, 10:20

Re: MoveTogether()

19 Mar 2019, 08:01

@wolf_II In fact no one would have contributed if this wasn't already useful :D

Hey I've been intrigued by the new version I was not aware of, so I made up a scenario that looked similar to my real script problem mentionned above.
In this example code, I used your latest Function version and add a new Gui window that is not in the Handles list object.
If there is not a check validation that we are dragging on a hWnd that is in the object list, it will still move the other 3 windows. But I think this would not be wanted in any case.
So what I have found that might help is to loop the handles list and compare with the hWnd being dragged.
If the dragged hWnd is not in the handle lsit then don't execute the repositionning of the other 3 windows.

What do you think ?

Code: Select all

#NoEnv
#SingleInstance, Force

Handles := []
Loop, 3 {
	Gui, %A_Index%: New, hwndhWin%A_Index%, Window %A_Index%
	Gui, Show, % "x" A_Index * 320 " y100 w250 h200"
	Handles.Push(hWin%A_Index%)
}
Gui, 4:New, hwndhWin4, Win#4 not in handles list
Gui, Show, % "x20 y100 w300 h600"

MoveTogether(Handles)
Return

GuiClose:
ExitApp

;--------------------------------------------------------------------------------
MoveTogether(wParam, lParam = "", _ = "", hWnd = "") { 	; using DeferWindowPos
;--------------------------------------------------------------------------------
    ; call MoveTogether(Handles) with an array of handles
    ; to set up a bundle of AHK Gui's that move together
    ;----------------------------------------------------------------------------
    ;												    - by Wolf_II
    ; https://www.autohotkey.com/boards/viewtopic.php?p=199402#p199402  
    ; version 2018.02.08
	
	static init := OnMessage(0xA1, "MoveTogether") ; WM_NCLBUTTONDOWN
	static Handles
	
	If IsObject(wParam)             ; detect a set up call
		Return, Handles := wParam   ; store the array of handles
	
	If (wParam != 2) ; HTCAPTION
		Return
	
	;================================================================================
	Match := False
	For each, Handle in Handles {
		if (Handle = hWnd) ; compare clicked hWnd to know if its one of the windows in the handles list
			Match := True	
	}
	If (Match) {
	;================================================================================
		
	   ; changing AHK settings here will have no side effects
		CoordMode, Mouse, Screen    ; for MouseGetPos
		SetBatchLines, -1           ; for onMessage
		SetWinDelay, -1             ; for WinActivate, WinMove
		
		M_old_X := lParam & 0xFFFF, M_old_Y := lParam >> 16 & 0xFFFF
		WinActivate, ahk_id %hWnd%
		
		Win := {}
		For each, Handle in Handles {
			WinGetPos, X, Y, W, H, ahk_id %Handle%
			Win[Handle] := {X: X, Y: Y, W: W, H: H}
		}
		
		While GetKeyState("LButton", "P") {
			MouseGetPos, M_new_X, M_new_Y
			dX := M_new_X - M_old_X, M_old_X := M_new_X
		   , dY := M_new_Y - M_old_Y, M_old_Y := M_new_Y
			
			If GetKeyState("Shift", "P")
				WinMove, ahk_id %hWnd%,, Win[hWnd].X += dX, Win[hWnd].Y += dY
			
			Else { ; DeferWindowPos cycle
				hDWP := DllCall("BeginDeferWindowPos", "Int", Handles.Length(), "Ptr")
				For each, Handle in Handles
					hDWP := DllCall("DeferWindowPos", "Ptr", hDWP
                    , "Ptr", Handle, "Ptr", 0
                    , "Int", Win[Handle].X += dX
                    , "Int", Win[Handle].Y += dY
                    , "Int", Win[Handle].W
                    , "Int", Win[Handle].H
                    , "UInt", 0x214, "Ptr")
				DllCall("EndDeferWindowPos", "Ptr", hDWP)
			}
		}
	}
}
wolf_II
Posts: 2688
Joined: 08 Feb 2015, 20:55

Re: MoveTogether()

19 Mar 2019, 08:18

@DRocks: looking good. This will also be useful, thanks for working this out and sharing! :thumbup: :)

Edit: In other words: Dude, you rock !!! :bravo:

I linked the new version in the OP.
DRocks
Posts: 518
Joined: 08 May 2018, 10:20

Re: MoveTogether()

19 Mar 2019, 09:48

Nice :D Glad I could collaborate with you. Thanks for your kind words.
wolf_II
Posts: 2688
Joined: 08 Feb 2015, 20:55

Re: MoveTogether()

19 Mar 2019, 11:34

I made some changes to my variable names and simplified the bit-operation for old_My.
I added some comments and DRocks' trick from above.
I then combined two Loops, and use the bail-out return as before, to be consistent.
I don't like to encase DllCall parameters in quotes any longer, I initially was distracted by them showing up in the ListVars window.
But I don't use DllCalls often enough to notice. And I believe they were put in to look nice ?

here is my version 2019.03.19

Code: Select all

#NoEnv
#SingleInstance Force

    ; ------------
    ; example code
    ; ------------

    Handles := []
    Loop, 3 { ; make 3 windows
        Gui, %A_Index%: New, hwndhWin%A_Index%, Window %A_Index%
        Gui, Show, % "x" A_Index * 320 " y100 w250 h200"
        Handles.Push(hWin%A_Index%)
    }

    Gui, New,, Window #4 NOT in Handles list
    Gui, Show, x320 y350 w400 h200

    MoveTogether(Handles)

return ; end of auto-execute section

GuiClose:
ExitApp



;-------------------------------------------------------------------------------
MoveTogether(wParam, lParam := 0, msg := 0, hWnd := 0) { ; using DeferWindowPos
;-------------------------------------------------------------------------------
    ; call MoveTogether(Handles) with an array of handles
    ; to set up a bundle of AHK Guis that move together
    ;---------------------------------------------------------------------------
    ; version 2019.03.19

    local ; protect superglobal vars like x, y, w, h, dx, dy or Win
    static init := OnMessage(0xA1, "MoveTogether") ; WM_NCLBUTTONDOWN
    static Handles

   ; changing AHK settings here will have no side effects
    CoordMode, Mouse, Screen    ; for MouseGetPos
    SetBatchLines, -1           ; for onMessage
    SetWinDelay, -1             ; for WinActivate, WinMove

    if IsObject(wParam)             ; detect a set up call
        return (Handles := wParam)  ; store the array of handles

    if (wParam != 0x2) ; HTCAPTION
        return ; only (part of) the title bar

    ; look for hWnd and check if it's on the list
    Win := {}, Found := False
    for each, Handle in Handles {
        WinGetPos, X, Y, W, H, ahk_id %Handle%
        Win[Handle] := {X: X, Y: Y, W: W, H: H}
        if (Handle = hWnd)
            Found := True
    }

    if not Found
        return ; not interested

    old_Mx := lParam & 0xFFFF
    old_My := lParam >> 16
    WinActivate, ahk_id %hWnd%

    while GetKeyState("LButton", "P") {
        MouseGetPos, new_Mx, new_My
        dx := new_Mx - old_Mx, old_Mx := new_Mx
      , dy := new_My - old_My, old_My := new_My

        if GetKeyState("Shift", "P") ; {Shift}-key moves independently
            WinMove, ahk_id %hWnd%,, Win[hWnd].X += dx, Win[hWnd].Y += dy

        else { ; DeferWindowPos cycle
            hDWP := DllCall("BeginDeferWindowPos", Int, Handles.Length(), Ptr)
            for each, Handle in Handles
                hDWP := DllCall("DeferWindowPos"
                    , Ptr, hDWP, Ptr, Handle, Ptr, 0
                    , Int, Win[Handle].X += dx
                    , Int, Win[Handle].Y += dy
                    , Int, Win[Handle].W
                    , Int, Win[Handle].H
                    , UInt, 0x214, Ptr)
            DllCall("EndDeferWindowPos", Ptr, hDWP)
        }
    }
}
Edit: added local keyword
Last edited by wolf_II on 19 Mar 2019, 16:56, edited 1 time in total.
DRocks
Posts: 518
Joined: 08 May 2018, 10:20

Re: MoveTogether()

19 Mar 2019, 13:41

@wolf_II

This is some very nice coding :D
I like the way you combine the loop and the clean code! Thanks again for this function.
I'll be putting it to good use
wolf_II
Posts: 2688
Joined: 08 Feb 2015, 20:55

Re: MoveTogether()

19 Mar 2019, 13:52

FYI: I don't know how the function will perform, when we use a larger amout of windows.
Combining the Loops, we waste potentially time before we bail out, for your trick to work.
I have not tested, In case you run into trouble, split the Loops again, and see if performance is better.


Just keep in mind: I have chosen to write nice code, not efficient code.
But for the given example, I did not notice any delay, You are more likely than me to find issues with it.

Also, I have not updated the OP, so people will find your solution first, I think that is appropriate.
DRocks
Posts: 518
Joined: 08 May 2018, 10:20

Re: MoveTogether()

19 Mar 2019, 15:53

Thanks for the pointer. I have not experienced any problems yet :)

a bit off-topic: Since one wise person reminded me that computers are made to work hard, I don't worry has much as before :D (this person was you :lol: )
And since I have tested more and more scenarios for performance times, I realise that most of the things that I THINK will be heavy on the computer don't even take 1ms to process :O
Computers are really impressive. When you think that a cycle can be as fast a 5ghz its almost unbelievable lol. For example, a 1 GHz processor has a cycle time of 1.0 ns and a 4 GHz processor has a cycle time of 0.25 ns.
This has helped me stop worrying about if I put a extra condition check or more precision in functions (before I thought that every line of code could make all the script be super slow).
Anyways, enough off-topic, I'll sure let you know if I see something performance-wise :)
wolf_II
Posts: 2688
Joined: 08 Feb 2015, 20:55

Re: MoveTogether()

19 Mar 2019, 16:55

Right, my mind is blown as well thinking about how much time computers spend doing nothing while waiting for the user to interact.

Thinking some more, I wonder if there is a net benefit one way or the other. It could be six of one, and half a dozen of the other.
If that theory holds, a single loop is preferable me thinks. :think:

I will add one tiny detail: the local keyword on its own at the beginning.
That way we can protect super-global variables, that have unfortunate interfering names, and we have no business in messing with them.
User avatar
DataLife
Posts: 326
Joined: 29 Sep 2013, 19:52

Re: MoveTogether()

27 Mar 2019, 05:33

wolf_II wrote:
19 Mar 2019, 11:34
I made some changes to my variable names and simplified the bit-operation for old_My.
I added some comments and DRocks' trick from above.
I then combined two Loops, and use the bail-out return as before, to be consistent.
I don't like to encase DllCall parameters in quotes any longer, I initially was distracted by them showing up in the ListVars window.
But I don't use DllCalls often enough to notice. And I believe they were put in to look nice ?

here is my version 2019.03.19

Code: Select all

#NoEnv
#SingleInstance Force

    ; ------------
    ; example code
    ; ------------

    Handles := []
    Loop, 3 { ; make 3 windows
        Gui, %A_Index%: New, hwndhWin%A_Index%, Window %A_Index%
        Gui, Show, % "x" A_Index * 320 " y100 w250 h200"
        Handles.Push(hWin%A_Index%)
    }

    Gui, New,, Window #4 NOT in Handles list
    Gui, Show, x320 y350 w400 h200

    MoveTogether(Handles)

return ; end of auto-execute section

GuiClose:
ExitApp



;-------------------------------------------------------------------------------
MoveTogether(wParam, lParam := 0, msg := 0, hWnd := 0) { ; using DeferWindowPos
;-------------------------------------------------------------------------------
    ; call MoveTogether(Handles) with an array of handles
    ; to set up a bundle of AHK Guis that move together
    ;---------------------------------------------------------------------------
    ; version 2019.03.19

    local ; protect superglobal vars like x, y, w, h, dx, dy or Win
    static init := OnMessage(0xA1, "MoveTogether") ; WM_NCLBUTTONDOWN
    static Handles

   ; changing AHK settings here will have no side effects
    CoordMode, Mouse, Screen    ; for MouseGetPos
    SetBatchLines, -1           ; for onMessage
    SetWinDelay, -1             ; for WinActivate, WinMove

    if IsObject(wParam)             ; detect a set up call
        return (Handles := wParam)  ; store the array of handles

    if (wParam != 0x2) ; HTCAPTION
        return ; only (part of) the title bar

    ; look for hWnd and check if it's on the list
    Win := {}, Found := False
    for each, Handle in Handles {
        WinGetPos, X, Y, W, H, ahk_id %Handle%
        Win[Handle] := {X: X, Y: Y, W: W, H: H}
        if (Handle = hWnd)
            Found := True
    }

    if not Found
        return ; not interested

    old_Mx := lParam & 0xFFFF
    old_My := lParam >> 16
    WinActivate, ahk_id %hWnd%

    while GetKeyState("LButton", "P") {
        MouseGetPos, new_Mx, new_My
        dx := new_Mx - old_Mx, old_Mx := new_Mx
      , dy := new_My - old_My, old_My := new_My

        if GetKeyState("Shift", "P") ; {Shift}-key moves independently
            WinMove, ahk_id %hWnd%,, Win[hWnd].X += dx, Win[hWnd].Y += dy

        else { ; DeferWindowPos cycle
            hDWP := DllCall("BeginDeferWindowPos", Int, Handles.Length(), Ptr)
            for each, Handle in Handles
                hDWP := DllCall("DeferWindowPos"
                    , Ptr, hDWP, Ptr, Handle, Ptr, 0
                    , Int, Win[Handle].X += dx
                    , Int, Win[Handle].Y += dy
                    , Int, Win[Handle].W
                    , Int, Win[Handle].H
                    , UInt, 0x214, Ptr)
            DllCall("EndDeferWindowPos", Ptr, hDWP)
        }
    }
}
Edit: added local keyword
In your latest example above some Gui's move behind non included windows.

To reproduce, run the above script and click on window 4 then click and drag window 1. Window 1 moves on top of window 4 and windows 2 and 3 move behind window 4. This also applies to any window that is not being dragged.
Check out my scripts. (MyIpChanger) (ClipBoard Manager) (SavePictureAs)
All my scripts are tested on Windows 10, AutoHotkey 32 bit Ansi unless otherwise stated.
wolf_II
Posts: 2688
Joined: 08 Feb 2015, 20:55

Re: MoveTogether()

27 Mar 2019, 06:27

DataLife wrote:
27 Mar 2019, 05:33
Thank you for the feed-back. I noticed this behaviour before.
Is this a feature request to include less surprises to the user?
Or is this a bug report? sorry, hobby programmer at work here :D
DRocks
Posts: 518
Joined: 08 May 2018, 10:20

Re: MoveTogether()

27 Mar 2019, 06:59

This is the mighty z-order here.

Clicking on win4 before dragging other windows will make win4 higher z-order than win 2-3. Win1 automatically becomes foreground because it is activated when clicking on it to drag.
Now if you click win 4 then click win2 for example, win1 and 3 will be hidden behind win4 because of the same z-order thing.

If you want all windows in the movetogether to be foreground, we would need to activate each windows in the list before the moving starts to happens
Edit: (to make them higher z-order than all other windows not in list)


Something like this works brute force: it activates all hwnds in the handles list automatically before dragging

Code: Select all

#NoEnv
#SingleInstance Force

    ; ------------
    ; example code
    ; ------------

Handles := []
Loop, 3 { ; make 3 windows
	Gui, %A_Index%: New, hwndhWin%A_Index%, Window %A_Index%
	Gui, Show, % "x" A_Index * 320 " y100 w250 h200"
	Handles.Push(hWin%A_Index%)
}

Gui, New,, Window #4 NOT in Handles list
Gui, Show, x320 y350 w400 h200

MoveTogether(Handles)

return ; end of auto-execute section

GuiClose:
ExitApp



;-------------------------------------------------------------------------------
MoveTogether(wParam, lParam := 0, msg := 0, hWnd := 0) { ; using DeferWindowPos
;-------------------------------------------------------------------------------
    ; call MoveTogether(Handles) with an array of handles
    ; to set up a bundle of AHK Guis that move together
    ;---------------------------------------------------------------------------
    ; version 2019.03.19
	
    local ; protect superglobal vars like x, y, w, h, dx, dy or Win
    static init := OnMessage(0xA1, "MoveTogether") ; WM_NCLBUTTONDOWN
    static Handles
	
   ; changing AHK settings here will have no side effects
    CoordMode, Mouse, Screen    ; for MouseGetPos
    SetBatchLines, -1           ; for onMessage
    SetWinDelay, -1             ; for WinActivate, WinMove
	
    if IsObject(wParam)             ; detect a set up call
        return (Handles := wParam)  ; store the array of handles
	
    if (wParam != 0x2) ; HTCAPTION
        return ; only (part of) the title bar
	
    ; look for hWnd and check if it's on the list
    Win := {}, Found := False
    for each, Handle in Handles {
		
		WinActivate, ahk_id %Handle%				; <<
        
		WinGetPos, X, Y, W, H, ahk_id %Handle%
        Win[Handle] := {X: X, Y: Y, W: W, H: H}
        
		if (Handle = hWnd)
			Found := True
	}
	
    if not Found
		return ; not interested
	
    old_Mx := lParam & 0xFFFF
    old_My := lParam >> 16
    WinActivate, ahk_id %hWnd%						; <<
	
    while GetKeyState("LButton", "P") {
        MouseGetPos, new_Mx, new_My
        dx := new_Mx - old_Mx, old_Mx := new_Mx
      , dy := new_My - old_My, old_My := new_My
		
        if GetKeyState("Shift", "P") ; {Shift}-key moves independently
            WinMove, ahk_id %hWnd%,, Win[hWnd].X += dx, Win[hWnd].Y += dy
		
        else { ; DeferWindowPos cycle
            hDWP := DllCall("BeginDeferWindowPos", Int, Handles.Length(), Ptr)
            for each, Handle in Handles
                hDWP := DllCall("DeferWindowPos"
                    , Ptr, hDWP, Ptr, Handle, Ptr, 0
                    , Int, Win[Handle].X += dx
                    , Int, Win[Handle].Y += dy
                    , Int, Win[Handle].W
                    , Int, Win[Handle].H
                    , UInt, 0x214, Ptr)
            DllCall("EndDeferWindowPos", Ptr, hDWP)
        }
    }
}
wolf_II
Posts: 2688
Joined: 08 Feb 2015, 20:55

Re: MoveTogether()

27 Mar 2019, 07:28

@DRocks: Dude, you rock! That's what I would call an elegant solution with fewer surprises to the user. :bravo:
@DataLife: I go with the solution above, brute-force is needed I think.
User avatar
DataLife
Posts: 326
Joined: 29 Sep 2013, 19:52

Re: MoveTogether()

27 Mar 2019, 07:33

thanks DRocks,

Code: Select all

WinActivate, ahk_id %hWnd%
works perfectly.
Check out my scripts. (MyIpChanger) (ClipBoard Manager) (SavePictureAs)
All my scripts are tested on Windows 10, AutoHotkey 32 bit Ansi unless otherwise stated.
DRocks
Posts: 518
Joined: 08 May 2018, 10:20

Re: MoveTogether()

27 Mar 2019, 09:02

Cool. Glad I could help :D thanks wolf :) !

Return to “Scripts and Functions”

Who is online

Users browsing this forum: No registered users and 73 guests