将 gui 停靠到变量 active window

docking gui to variable active window

我看到很多示例允许我打开程序并使用 winwait 然后 winexist 将 gui 停靠到它。

相反,我想做的是将我的图形用户界面停靠在 window 处于活动状态的位置。我尝试了一百万种方法,请帮忙。

(同样,当我确实将其附加到 window 时,它并没有完美地居中生成,因为在主脚本中,每当我尝试这样做时:

        WinGetPos cX, cY, cW, cH, ahk_id %ChildhWnd% 
        offset1 := (mw / 2) - (cw /2)

当我按下热键时它抛出一个错误说 x y 无效

以下:我没有完全自己制作的脚本。我自己想出了数学,但从我不记得的资源中大量借用了。它几乎可以满足我的需求,但还不够

; #Warn  ; Enable warnings to assist with detecting common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
#SingleInstance, force

^T::
hookProcAdr := RegisterCallback("HookProc")
hHook := SetWinEventHook(0x800B,0x800B,0,hookProcAdr,0,0,0) ; EVENT_OBJECT_LOCATIONCHANGE 

Gui, +hwndChildhWnd +AlwaysOnTop
Gui, add, text,,some text in a small gui that will move around with a notepad window
Gui, add, Button,,Button

MainhWnd := WinExist() ;<---------what do i do here?

;--------------------------------------------;
;-----i commented my second problem here-----;
;--------------------------------------------;

WinGetPos, mX, mY, mW, mH, ahk_id %MainhWnd%
;WinGetPos cX, cY, cW, cH, ahk_id %ChildhWnd% <--------{why cant i put child window here-+
;                                                                                        |
offset2 := (mw / 2) ; - (cw /2)  <------------------------with offset subtraction here <-+

cX := mX + offset2
cY := mY

Gui, show, x%cX% y%cY%

return

;-------------------;
;-----functions-----;
;-------------------;

HookProc(hWinEventHook, event, hwnd) 
{ 
    global   MainHwnd, ChildhWnd 
    if (hwnd = MainHwnd)
    {
        SetWinDelay, -1
        WinGetPos hX, hY, hW, hH, ahk_id %MainhWnd% 
        WinGetPos cX, cY, cW, cH, ahk_id %ChildhWnd% 
        
        offset1 := (hw / 2) - (cw / 2)
        X := hX + offset1
        Y := hY

        WinMove ahk_id %ChildhWnd%,,X,Y,w%cw%,h%ch%
    }
} 

SetWinEventHook(eventMin, eventMax, hmodWinEventProc, lpfnWinEventProc, idProcess, idThread, dwFlags) { 
   DllCall("CoInitialize", "uint", 0) 
   return DllCall("SetWinEventHook", "uint", eventMin, "uint", eventMax, "uint", hmodWinEventProc, "uint", lpfnWinEventProc, "uint", idProcess, "uint", idThread, "uint", dwFlags) 
}```

那里几乎没有错误,window 切换部分必须添加一些额外的逻辑和魔法。
此外,我打算将此脚本从旧语法转换为使用现代表达式语法。

所以首先,在这一行 MainhWnd := WinExist()
我们想从当前活动的 window 开始,所以让我们像这样使用 A (docs)
MainhWnd := WinExist("A")

然后这部分代码:

WinGetPos, mX, mY, mW, mH, ahk_id %MainhWnd%
;WinGetPos cX, cY, cW, cH, ahk_id %ChildhWnd% <--------{why cant i put child window here-+
;                                                                                        |
offset2 := (mw / 2) ; - (cw /2)  <------------------------with offset subtraction here <-+

cX := mX + offset2
cY := mY

Gui, show, x%cX% y%cY%

这里你无法获取child window的位置,因为它还不存在。
如果 gui 是 pre-determined,你可以知道它的宽度是多少。
如果它的宽度没有改变,也没有必要在每次 window 移动时都获取它的宽度。
所以也许只需将预定宽度存储在一个变量中就可以了。

如果无法预先确定宽度,例如,您可以快速显示图形用户界面并抓住它的宽度,然后立即移动它。

ChildWindowWidthHalf := 349/2

WinGetPos, mX, mY, mW, , % "ahk_id " MainhWnd
offset2 := (mw / 2) - ChildWindowWidthHalf

cX := mX + offset2
cY := mY

Gui, show, % "x" cX " y" cY

在这里您还可以看到我第一次出现放弃旧语法并切换到 expression,例如这里连接一个字符串和一个变量:
ahk_id %MainhWnd%% "ahk_id " MainhWnd
基本上,一个 % 后跟一个 space,强制命令的参数计算一个表达式,而不是期望一个遗留文本参数。
如果这一切对您来说都是全新的,我建议您查看文档中的 this 页面。


然后是HookProc函数中的问题:

HookProc(hWinEventHook, event, hwnd) 
{ 
    global   MainHwnd, ChildhWnd 
    if (hwnd = MainHwnd)
    {
        SetWinDelay, -1
        WinGetPos hX, hY, hW, hH, ahk_id %MainhWnd% 
        WinGetPos cX, cY, cW, cH, ahk_id %ChildhWnd% 
        
        offset1 := (hw / 2) - (cw / 2)
        X := hX + offset1
        Y := hY

        WinMove ahk_id %ChildhWnd%,,X,Y,w%cw%,h%ch%
    }
} 

先到这里
global MainHwnd, ChildhWnd
我们可以放弃变量 MainHwnd,因为它对我们没有用。我们希望 MainHwnd 成为当前活动的 window,而不是我们在脚本开始时确定的那个 window。
并且让我们添加这个变量 ChildWindowWidthHalf.

if (hwnd = MainHwnd)
这张支票对我们来说也没有意义,应该完全删除。
虽然,我强烈建议用这个 if (hwnd = WinActive("A")) 替换它,这样你就可以过滤掉与当前活动 window 移动无关的消息。

这个这个:
SetWinDelay, -1
没有理由每次都设置它,我们可以在脚本顶部设置一次。

在此:

WinGetPos hX, hY, hW, hH, ahk_id %MainhWnd% 
WinGetPos cX, cY, cW, cH, ahk_id %ChildhWnd% 
        
offset1 := (hw / 2) - (cw / 2)

我们对 MainhWnd 不感兴趣,如上所述,让我们用直接从 windows 消息中获得的 hwnd 替换它。传递给 HookProc 函数的第三个参数的 hwnd。
WinGetPos, hX, hY, hW, , % "ahk_id " hwnd
然后我们就可以摆脱获取 child window 的位置。没用。
然后让我们也切换偏移量以再次使用预定的 child window 宽度:
offset1 := (hW / 2) - ChildWindowWidthHalf

然后进入WinMove command
WinMove ahk_id %ChildhWnd%,,X,Y,w%cw%,h%ch%
它被错误地使用了。宽度和高度参数前面没有 wh 字符。而且我们没有理由改变 window 的宽度或高度。它应该是这样的:
WinMove, % "ahk_id " ChildhWnd, , % X, % Y


And now you should have a working script as follows:

SetWinDelay, -1

hookProcAdr := RegisterCallback("HookProc")
hHook := SetWinEventHook(0x800B,0x800B,0,hookProcAdr,0,0,0) ; EVENT_OBJECT_LOCATIONCHANGE 

Gui, +hwndChildhWnd +AlwaysOnTop
Gui, add, text, , % "some text in a small gui that will move around with a notepad window"
Gui, add, Button, , Button


MainhWnd := WinExist("A")

ChildWindowWidthHalf := 349/2

WinGetPos, mX, mY, mW, , % "ahk_id " MainhWnd
offset2 := (mw / 2) - ChildWindowWidthHalf

cX := mX + offset2
cY := mY

Gui, show, % "x" cX " y" cY
return

HookProc(hWinEventHook, event, hwnd) 
{ 
    global ChildhWnd, ChildWindowWidthHalf
    if (hwnd = WinActive("A"))
    {
        WinGetPos, hX, hY, hW, , % "ahk_id " hwnd 
        
        offset1 := (hW / 2) - ChildWindowWidthHalf
        X := hX + offset1
        Y := hY
        
        WinMove, % "ahk_id " ChildhWnd, , % X, % Y
    }
} 

SetWinEventHook(eventMin, eventMax, hmodWinEventProc, lpfnWinEventProc, idProcess, idThread, dwFlags) 
{ 
   DllCall("CoInitialize", "uint", 0) 
   return DllCall("SetWinEventHook", "uint", eventMin, "uint", eventMax, "uint", hmodWinEventProc, "uint", lpfnWinEventProc, "uint", idProcess, "uint", idThread, "uint", dwFlags) 
}

但它只会在 window 移动时更新 child window 的位置,而不是当您激活另一个 window.[=75= 时] 因此,您还需要 window 获得焦点。
这可以通过 shell 钩子很好地完成。
我将在代码中用注释来解释这一点,这里是相关文档链接:
RegisterShellHookWindow, RegisterWindowMessage, OnMessage

;register our script to receive shell messages
DllCall("RegisterShellHookWindow", UInt, ChildhWnd)

;define a unique message which we'll monitor
MsgId := DllCall("RegisterWindowMessage", Str, "SHELLHOOK")

;monitors the the message and fires the our 
;user defined function "MsgMonitor"
OnMessage(MsgId, "MsgMonitor")

MsgMonitor(wParam, lParam) ;wParam = message, lParam = hwnd
{
    ;HSHELL_WINDOWACTIVATED = 0x04
    ;HSHELL_RUDEAPPACTIVATED = 0x8004
    if (wParam = 0x8004 || wParam = 0x8004) 
        ;we can borrow the HookProc function so we dont
        ;need to type the same code again
        ;first two parameters arent needed
        HookProc("whatever", "whatever", lParam)
}

And now finally you should arrive at a finished product like this:

SetWinDelay, -1

hookProcAdr := RegisterCallback("HookProc")
hHook := SetWinEventHook(0x800B,0x800B,0,hookProcAdr,0,0,0) ; EVENT_OBJECT_LOCATIONCHANGE 

Gui, +hwndChildhWnd +AlwaysOnTop
Gui, add, text, , % "some text in a small gui that will move around with a notepad window"
Gui, add, Button, , Button

;register our script to receive shell messages
DllCall("RegisterShellHookWindow", UInt, ChildhWnd)
;define a unique message which we'll monitor
MsgId := DllCall("RegisterWindowMessage", Str, "SHELLHOOK")
;monitors the the message and fires the our 
;user defined function "MsgMonitor"
OnMessage(MsgId, "MsgMonitor")

MainhWnd := WinExist("A")

ChildWindowWidthHalf := 349/2

WinGetPos, mX, mY, mW, , % "ahk_id " MainhWnd
offset2 := (mw / 2) - ChildWindowWidthHalf

cX := mX + offset2
cY := mY

Gui, show, % "x" cX " y" cY
return

MsgMonitor(wParam, lParam) ;wParam = message, lParam = hwnd
{
    ;HSHELL_WINDOWACTIVATED = 0x04
    ;HSHELL_RUDEAPPACTIVATED = 0x8004
    if (wParam = 0x8004 || wParam = 0x8004) 
        ;we can borrow the HookProc function so we dont
        ;need to type the same code again
        ;first two parameters arent needed
        HookProc("whatever", "whatever", lParam)
}

HookProc(hWinEventHook, event, hwnd) 
{ 
    global ChildhWnd, ChildWindowWidthHalf
    if (hwnd = WinActive("A"))
    {
        WinGetPos, hX, hY, hW, , % "ahk_id " hwnd 
        
        offset1 := (hW / 2) - ChildWindowWidthHalf
        X := hX + offset1
        Y := hY
        
        WinMove, % "ahk_id " ChildhWnd, , % X, % Y
    }
} 

SetWinEventHook(eventMin, eventMax, hmodWinEventProc, lpfnWinEventProc, idProcess, idThread, dwFlags) 
{ 
   DllCall("CoInitialize", "uint", 0) 
   return DllCall("SetWinEventHook", "uint", eventMin, "uint", eventMax, "uint", hmodWinEventProc, "uint", lpfnWinEventProc, "uint", idProcess, "uint", idThread, "uint", dwFlags) 
}