MoveTogether()

Post your working scripts, libraries and tools
wolf_II
Posts: 2226
Joined: 08 Feb 2015, 20:55

MoveTogether()

22 Jan 2018, 16:11

Here is a function that moves windows AHK Guis together:
Spoiler
Example:

Code: Select all

#NoEnv
#SingleInstance Force
#Include, MoveTogether.ahk

    Handles := []

    Loop, 3 {
        Gui, %A_Index%: New, hwndhWin%A_Index%, Window %A_Index%
        Gui, Show, % "x" A_Index * 280 " y100 w250 h200"
        Handles.Push(hWin%A_Index%)
    }

Return

GuiClose:
ExitApp
Grab one of the windows AHK Guis by the titlebar and move it around. The others will move together with it.

Just push all handles of interest into a global array called Handles, and include the function.
The example includes inside the autoexecute section, which will run three lines of "modifying".
The function could read the current settings, modify as needed and restore afterwards.

Thx to Lexikos for teaching how to use DeferWindowPos.
https://autohotkey.com/boards/viewtopic ... rWindowPos

EDIT: clarified the fact that function only works for AHK Gui's
EDIT2: keep reading, what follows is a collaboration to improve the function, or see the improved function here.
Edit3: added a link to Lexikos' teaching session
Last edited by wolf_II on 12 Feb 2018, 09:31, edited 3 times in total.
digidings
Posts: 5
Joined: 22 Jan 2018, 17:04

Re: MoveTogether()

22 Jan 2018, 17:10

Nice code! :bravo:

... just a small modification in the function MoveTogether so that when Shift is held down it's possible to move a single window out of the group e.g. for rearranging ...

Code: Select all

While GetKeyState("LButton") and !GetKeyState("Shift") {
cheers :thumbup:
User avatar
derz00
Posts: 497
Joined: 02 Feb 2016, 17:54
GitHub: derz00
Location: Middle of the round cube

Re: MoveTogether()

22 Jan 2018, 17:24

Hey, this looked interesting, but didn't work very well.

https://tinyurl.com/y7vgkxk9 Dropbox doesn't play gifs but you can download
try it and see
...
wolf_II
Posts: 2226
Joined: 08 Feb 2015, 20:55

Re: MoveTogether()

22 Jan 2018, 17:54

digidings wrote:While GetKeyState("LButton") and !GetKeyState("Shift")
Excellent idea! Thanks for sharing, I'm glad you like it. :D
wolf_II
Posts: 2226
Joined: 08 Feb 2015, 20:55

Re: MoveTogether()

22 Jan 2018, 17:56

@derz00: I don't understand, I can see a single image, no video.
User avatar
derz00
Posts: 497
Joined: 02 Feb 2016, 17:54
GitHub: derz00
Location: Middle of the round cube

Re: MoveTogether()

22 Jan 2018, 18:05

I know. You have to download the gif. Dropbox doesn't play it.

Here's a link to webm, if that's easier. https://preview.tinyurl.com/derz00Dropbox

I don't know what's causing this problem... Seems to be a nice idea though.

Edit: I found the problem: I was running the code in CodeQuickTester, that runs the script though a "pipe". Not sure why that made a difference. Sorry for the disturbance. :cry:

:wave:
try it and see
...
guest3456
Posts: 2464
Joined: 09 Oct 2013, 10:31

Re: MoveTogether()

23 Jan 2018, 11:08

i've used majkinetor's DockA script in the past for this use-case. not sure whether i like your solution better or not. I don't really like your global Handles var. DockA lets me pass individual hwnds in. anyway, its good work. two things:

1. i dont think you should be globally setting SetBatchLines, CoordMode, etc. i think you should save the old values, then change them in the func, and then change them back:

Code: Select all

oldwindelay := A_WinDelay
SetWinDelay, -1
;... your code
SetWinDelay, %oldwindelay%
that way your function has no side effects

maybe it doesn't matter much for WinDelay and BatchLines, but it is pretty important not to change CoordMode

2. i dont think you should claim it moves "windows" together, but rather "ahk guis". the NCLBUTTONDOWN msg will only fire on your AHK guis, not other windows

Helgef
Posts: 3300
Joined: 17 Jul 2016, 01:02
Contact:

Re: MoveTogether()

23 Jan 2018, 11:45

Nice one thanks for sharing :thumbup:

As guest3456 indicated, you do not need global variables or settings. The Handles can be passed as the first parameter and stored in a static variable, and the settings can be set inside the function. Since OnMessage callbacks always run in new threads, there is no need to restore settings before the function returns. Example,

Code: Select all

#NoEnv
#SingleInstance Force
; #Include, MoveTogether.ahk

    Handles := []

    Loop, 3 {
        Gui, %A_Index%: New, hwndhWin%A_Index%, Window %A_Index%
        Gui, Show, % "x" A_Index * 280 " y100 w250 h200"
        Handles.Push(hWin%A_Index%)
    }
	MoveTogether(Handles)
Return

GuiClose:
ExitApp



;-------------------------------------------------------------------------------
MoveTogether(wParam, lParam:="", _:="", hWnd:="") { ; using DeferWindowPos
;-------------------------------------------------------------------------------
    static init := OnMessage(0x00A1, "MoveTogether") ; WM_NCLBUTTONDOWN
    static Handles
	if isObject(wParam)
		return Handles := wParam
    IfNotEqual, wParam, 2, Return ; HTCAPTION (only part of title bar)
	;---------------------------------------------
    CoordMode, Mouse, Screen    ; for MouseGetPos
    SetBatchLines, -1           ; for onMessage
    SetWinDelay, -1             ; for WinActivate
    ;---------------------------------------------
    M_old_X := lParam & 0xFFFF, M_old_Y := lParam >> 16 & 0xFFFF
    WinActivate, ahk_id %hWnd%

    For each, Handle in Handles
        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, % Handles.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", Handles.Length(), "Ptr")
        For each, Handle in Handles
            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)
    }
}
Cheers.
wolf_II
Posts: 2226
Joined: 08 Feb 2015, 20:55

Re: MoveTogether()

23 Jan 2018, 12:59

@guest3456 and Helgef Thank you for your feedback!

I wrote first a similar function for a related project (class Spinner), where Spinner.Handles is accessible in the function since Spinner is a superglobal. (not sure if that's the right expression)
In my use-case, I don't use the global, I put it in the general function as a hint how to use the function. Obviously a better approach to make it more general is how Helgef is showing, Thanks for that, btw.
I had a look at DockA as well and I don't understand most of it, but it looks very professional. I'm sure DockA will sort things out correctly, where my simple spinner won't need to get anything done at all.
DockA comments wrote:windows will behave like single window - moving, sizing, focusing, hiding and other OS events will be handled by the module so that the "composite window" behaves like the single window.
The intended purpose of my function is to allow for several spinners to be shown and move them around fast when I move the top-level GUI.
Thank you very much for the tips how to store and restore settings. I will keep this good advice in mind.
wolf_II
Posts: 2226
Joined: 08 Feb 2015, 20:55

Re: MoveTogether()

23 Jan 2018, 13:12

I only just realized the truth of what guest3456 writes about non-AHK windows: they don't care about or respond to my onMeesage. I will edit the OP.
Thx again for the insight. :thumbup:
User avatar
Nextron
Posts: 1318
Joined: 01 Oct 2013, 08:23
Location: Netherlands OS: Win7 x64 AHK: Unicode x32

Re: MoveTogether()

23 Jan 2018, 16:02

Nice, I used an application which has an option similar to this, but doesn't fully suit my needs. Using AHK with WinMove to mimic it just didn't look smooth enough, but this looks good and probably is the same method used by that other app.

I think with WinEventProc using EVENT_SYSTEM_MOVESIZESTART and EVENT_SYSTEM_MOVESIZEEND as a trigger, you can make it work for non-AHK windows too.
wolf_II
Posts: 2226
Joined: 08 Feb 2015, 20:55

Re: MoveTogether()

23 Jan 2018, 16:28

@ Nextron: Nice hint, thank you very much. I want my spinner to attach to the Edit1 control of notepad.exe as one of the demos.
I will have to do more homework to figure out the details for that.

I use currently SetParent to take care of the movement with some styles applied to my Gui to allow click through. That seems to work perfectly on Win10 but not on Win7.
guest3456
Posts: 2464
Joined: 09 Oct 2013, 10:31

Re: MoveTogether()

24 Jan 2018, 11:45

Nextron wrote: Using AHK with WinMove to mimic it just didn't look smooth enough, but this looks good and probably is the same method used by that other app
wolf_2 is using DeferWindowPos, which doesn't suffer from the lagging of doing WinMove within a loop.

iPhilip
Posts: 317
Joined: 02 Oct 2013, 12:21

Re: MoveTogether()

26 Jan 2018, 13:38

Hi Wolf_II,

Thank you for your work. While it is true that the function moves AutoHotkey Guis together, if the window being dragged is a AutoHotkey Gui, it will also move any other window whose handle has been added to the Handles array. The script below demonstrates that with an additional Notepad window. I took the liberty of simplifying the function a bit by using an associative array and added the ability of pressing the Shift key to disable moving the other windows. Here is the script:

Code: Select all

#NoEnv
#SingleInstance Force

Handles := []

Loop, 3 {
   Gui, %A_Index%: New, Resize hwndhWin, Window %A_Index%
   Gui, Show, % "x" A_Index * 280 " y100 w250 h200"
   Handles.Push(hWin)
}

Run, Notepad, , , pid
WinWaitActive, ahk_pid %pid%
WinMove, A, , 2*280, 395, 330, 295
Handles.Push(WinExist("A"))

MoveTogether(Handles)
Return

Esc::
GuiClose:
2GuiClose:
3GuiClose:
WinClose, ahk_pid %pid%
ExitApp

;-------------------------------------------------------------------------------
MoveTogether(wParam, lParam:="", _:="", hWnd:="") { ; using DeferWindowPos
;-------------------------------------------------------------------------------
   static init := OnMessage(0x00A1, "MoveTogether") ; WM_NCLBUTTONDOWN
   static Handles
   if isObject(wParam)
     return Handles := wParam
   IfNotEqual, wParam, 2, Return ; HTCAPTION (only part of title bar)
   ;---------------------------------------------
   Prev_CoordModeMouse := A_CoordModeMouse
   CoordMode, Mouse, Screen    ; for MouseGetPos
   ;---------------------------------------------
   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

      ; 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

      ; DeferWindowPos cycle
      if (dX || dY) {
         if GetKeyState("Shift", "P")
            WinMove, ahk_id %hWnd%, , Win[hWnd].X += dX, Win[hWnd].Y += dY
         else {
            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", 0x0214, "Ptr")
            DllCall("EndDeferWindowPos", "Ptr", hDWP)
         }
      }
   }
   CoordMode, Mouse, %Prev_CoordModeMouse%
}
Cheers!
Windows 7 Pro (64 bit) - AutoHotkey v1.1+ (Unicode 32-bit)
wolf_II
Posts: 2226
Joined: 08 Feb 2015, 20:55

Re: MoveTogether()

26 Jan 2018, 14:18

@ iPhilip: Thank you very much. :thumbup:
I noticed how nicely you dealt with the associative array and the ingenious idea to put all computations where they are needed. Removing one of three loops. :dance:
garry
Posts: 1563
Joined: 22 Dec 2013, 12:50

Re: MoveTogether()

08 Feb 2018, 04:42

@wolf_II , thank you for the script
just tried to add x y w h variable to the last example from iPhilip
( define the first and followed positions )
EDIT1 : attached 3 ahk GUI examples and 2 programs
EDIT2 : used last changed function
EDIT3: added a 2nd example : Listbox , send selected commands to DOS

Code: Select all

;- modified = 20180212
;- created  = 20180208
#warn
#NoEnv
;#SingleInstance Force
setworkingdir,%a_scriptdir%
DetectHiddenWindows, ON

Handles := []

i=0
x1:=1
y1:=1
w1:=350
w1a:=(w1+7)
h1:=200

aa=a1,a2,a3
stringsplit,a,aa,`,
           total1:=a0

;----- 3 AHK-GUI together ----------------------------------------
Loop, %total1%
   {
   Gui, %A_Index%: New,  hwndhWin%A_Index%, Window %A_Index%
   c:= % A%a_index%
   if i=0
     {
     gosub,%c%
     Gui, Show, x%x1% y%y1% w%w1% h%h1%,TEST-%a_index% ( -drag- )
     }
   else
     {
     gosub,%c%
     Gui, Show, % "x" (I*w1a)+x1 . "y" . y1 . "w" . w1 . " h" . h1,TEST-%a_index% ( -drag- )
     }
   Handles.Push(hWin%A_Index%)
   i++
   }
;-------------------------------------------------------------------

;----------- next- attached window  NOTEPAD  ----------
diff:=27
x2:=x1
y2:=(y1+h1+diff)
w2:=(3*w1a)
h2:=200
Run, Notepad, , , pid1
WinWaitActive, ahk_pid %pid1%
sleep,200
WinMove, ahk_pid %pid1%, , x2, y2, w2, h2

;----------- next- attached window  DOS  ----------
x3:=x1
y3:=(y2+h2)
w3:=(3*w1a)
h3:=500

e4x=
(ltrim join&
dir
ver
)
title2=AutoHotkey DOS
;Run, %COMSPEC% /k %e4x%,,hide,pid2

Run %COMSPEC% /k "title %title2% & mode con lines=4048 cols=254&COLOR 02&CD\",,,pid2   ; define title / lines / colons / color

WinWaitActive, ahk_pid %pid2%
sleep,200
WinMove, ahk_pid %pid2%, , x3, y3, w3, h3
sleep,200
controlsend,,ver`n,ahk_pid %pid2%
;--------------------------------------------------------
Handles.Push(WinExist("A"))    ;- dos is lastactive

IfWinNotActive ,ahk_pid %pid1%,,WinActivate,ahk_pid %pid1%
  WinWaitActive,ahk_pid %pid1%

Handles.Push(WinExist("A"))    ;- add notepad also
controlsend,,Line1`nLine2`n,ahk_pid %pid1%

MoveTogether(Handles)
Return


Esc::
GuiClose:
2GuiClose:
3GuiClose:
WinClose, ahk_pid %pid1%
WinWaitClose,ahk_pid %pid1%
WinClose, ahk_pid %pid2%
WinWaitClose,ahk_pid %pid2%
ExitApp

;==================== 3 GUI-Examples =====================================
a1:
Gui,1:default
;Gui,1: +AlwaysOnTop  -Sysmenu
Gui,1: -Sysmenu
Gui,1:Color,Black
Gui,1:Color,ControlColor, Black
Gui,1:Font,s12 cYellow ,Lucida Console
Gui,1:add,Edit,   x20   y10  w320 h100    left   vInp1 ,test_1
Gui,1:add,Button, x20   y130 w100 h26  vB1 gB1 , Button1
return

a2:
Gui,2:default
;Gui,2: +AlwaysOnTop  -Sysmenu
Gui,2: -Sysmenu
Gui,2:Color,Gray
Gui,2:Color,ControlColor, Gray
Gui,2:Font,s14 cBlack ,Lucida Console
Gui,2:add,Edit,   x20   y10  w320 h100    left   vInp2 ,test_2
Gui,2:add,Button, x20   y130 w100 h26  vB2 gB2 , Button2
return

a3:
Gui,3:default
;Gui,3: +AlwaysOnTop
Gui,3:Font,cDefault ,Fixedsys
Gui,3:Color,Teal
Gui,3:Color,ControlColor, Teal
Gui,3:add,Edit,   x20   y10  w320 h100  cBlack   left   vInp3 ,test_3
Gui,3:add,Button, x20   y130 w100 h26  vB3 gB3 , Button3
return

B1:
msgbox,Button1
return
B2:
msgbox,Button2
return
B3:
msgbox,Button3
return

;========== END 3- GUI examples ===========================================



;-------------------------------------------------------------------------------
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
    ;---------------------------------------------------------------------------
    ; https://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

    ; 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)
        }
    }
}
;================================= END SCRIPT ========================================

2nd Example : Listbox , send selected commands to DOS

Code: Select all

;-------- https://autohotkey.com/boards/viewtopic.php?t=43192 -------------
;- script moveTogether() with 3 attached ahk-examples  > from user= Wolf_II

;--------- LISTBOX send selected  commands to DOS --------------------------
;- example : Listbox with attached DOS Window
;            drag at Listbox to move both windows
;            close Listbox > also closes DOS
;---------------------------------------------------------------------------

;-modified = 20180212
;-created  = 20180211
;-https://autohotkey.com/boards/viewtopic.php?f=5&t=44098
;---------------------------------------------------------------------------

#warn
#NoEnv
;#SingleInstance Force
setworkingdir,%a_scriptdir%
DetectHiddenWindows, ON
SetKeyDelay,20,20
sendmode,input

Handles := []

;------------ ahk script Listbox --------------------
i=0
x1:=0
y1:=0
w1:=200
h1:=935

lb=
(ltrim join|
cd\
ver
dir
vol
cmd /?
help
date /t
time /t
path
ping /?
ping -n 4 -w 1000 www.google.com
netstat /?
netstat
netstat -an
netstat -e -s
ipconfig /?
ipconfig /all
logoff /?
shutdown /?
sfc /?
mem /?
copy /?
xcopy /?
mode /?
label /?
start /?
subst /?
verify /?
Format /?
fc /?
find /?
findstr /?
convert /?
color /?
at /?
attrib /?
)
sort,lb,D|

   ;- create ahk GUI = Listbox
   Gui,1: New, hwndhWin, Window 1
   Gui,1:default
   Gui,1:Color,Black
   Gui,1:Color,ControlColor, Black
   Gui,1:Font,s12 cYellow ,Lucida Console
   Gui,1:add,Listbox,x%x1% y%y1% w%w1% h%h1% gLb1 vLbx cYellow,%lb%
   Gui,1:Show,       x%x1% y%y1% w%w1% h%h1%,Drag & Close here

   Handles.Push(hWin)

;----------- next-attached window > DOS ----------
x3:=205
y3:=1
w3:=1220
h3:=950

;-- example-1 send to DOS
e4x=
(Ltrim Join&
cd\
dir
)
title2=AutoHotkey DOS
;Run, %COMSPEC% /k %e4x%,,,pid2       ;- start DOS and send different commands
;----------------------------
;- example-2
Run, %COMSPEC% /S /U /Q /F:ON /k "title %title2% & mode con lines=4048 cols=120&COLOR 02&CD\",,,pid2   ; define title / lines / colons / color
;----------------------------
WinWaitActive, ahk_pid %pid2%
sleep,200
WinMove, ahk_pid %pid2%, , x3, y3, w3, h3                     ;- move DOS to the right position
sleep,200
sendinput,ver`n                                               ;- send another command to DOS if needed

IfWinNotActive ,ahk_pid %pid2%,,WinActivate,ahk_pid %pid2%    ;- Activate DOS
  WinWaitActive,ahk_pid %pid2%

Handles.Push(WinExist("A"))                                   ;- DOS is lastactive
MoveTogether(Handles)
Return
;-------------------------------------------------------------------------------------------------------------

Guiclose:                                                      ;- when close Listbox-GUI close also attached DOS
WinClose, ahk_pid %pid2%                                       ;- if close only DOS , ahk-script freeze but you can close it
WinWaitClose,ahk_pid %pid2%
exitapp
;------------------------------------

Lb1:                                                           ;- send commands to DOS and let DOS active
Gui,1:submit,nohide
IfWinNotActive ,ahk_pid %pid2%,,WinActivate,ahk_pid %pid2%
  WinWaitActive,ahk_pid %pid2%
controlsend,,`n%lbx%`n,ahk_pid %pid2%                          ;- send command from Listbox to DOS
return
;==============================================================================


;-------------------------------------------------------------------------------
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
    ;---------------------------------------------------------------------------
    ; https://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

    ; 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)
        }
    }
}
;=================================== END script ====================================
Last edited by garry on 12 Feb 2018, 13:19, edited 5 times in total.
wolf_II
Posts: 2226
Joined: 08 Feb 2015, 20:55

Re: MoveTogether()

08 Feb 2018, 16:07

Thanks to all contributors for your collaboration. Team work for the win :thumbup: :D :bravo:

I have taken the time to read all the suggestion for improvement again. So here is, hopefully, a generally useful function:
  • an array of handles can be passed to the function to set up, thx Helgef
  • no more worries about settings leaking, thx guest3456 for raising the issue, thx Helgef for solving it
  • implemented Shifting, thx digidings for suggeting and iPhilip for improving
  • rewrite using Win{}, thx iPhilip

Code: Select all



;-------------------------------------------------------------------------------
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
    ;---------------------------------------------------------------------------
    ; https://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

    ; 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)
        }
    }
}
Example:

Code: Select all

#NoEnv
#SingleInstance, Force
#Include, MoveTogether.ahk

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

Return

GuiClose:
ExitApp
I also added a link in the first post.

Edit: added comments: version and link to here
JasonDavisFL
Posts: 9
Joined: 12 Jan 2018, 13:05
Facebook: JasonDavis83
GitHub: JasonDavisFL

Re: MoveTogether()

17 Feb 2018, 20:57

This is awesome! Anyway to minimize all at once and then restore?
wolf_II
Posts: 2226
Joined: 08 Feb 2015, 20:55

Re: MoveTogether()

18 Feb 2018, 14:30

Try this:

Code: Select all

#NoEnv
#SingleInstance, Force
    Handles := []
    Loop, 3 {
        Gui, %A_Index%: New, hwndhWin%A_Index%, Window %A_Index%
        Gui, Show, % "x" A_Index * 280 " y100 w250 h200"
        Handles.Push(hWin%A_Index%)
    }
    MoveTogether(Handles)
    OnMessage(0x5, "WM_SIZE")
Return

GuiClose:
ExitApp

WM_SIZE(wParam) {
    Loop, 3
            Gui, % A_Index (wParam = 1 ? ":Minimize" : ":Show")
}


;-------------------------------------------------------------------------------
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
    ;---------------------------------------------------------------------------
    ; https://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

    ; 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)
        }
    }
}
I hope that helps.
elmo
Posts: 81
Joined: 09 Oct 2013, 09:08

Re: MoveTogether()

12 Sep 2018, 08:31

@wolf_II

This is soooo good. Thank you for sharing.

Return to “Scripts and Functions”

Who is online

Users browsing this forum: arcticir, gwarble, Milchmann and 65 guests