将gui停靠到可变活动窗口

问题描述

我看到了许多示例,这些示例允许我打开程序并使用winwait,然后使用winexist将gui停靠到该程序。

相反,我想做的是将gui停靠在任何活动窗口上。香港专业教育学院尝试了一百万种方法,请帮忙。

(同样,当我确实将它附加到窗口时,它也不会完全居中出现,因为在主脚本中,只要我尝试这样做:

GET,HEAD

当我按热键说x y无效时,它将引发错误

以下:我自己没有完全编写的脚本。我自己想出了数学方法,但从我不记得的资料中大量借用了数学。它几乎可以满足我的需求,但不能完全满足

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

解决方法

在那里几乎没有错误,并且必须在窗口切换部分添加一些额外的逻辑和魔法。
另外,我将把这个脚本转换成传统语法以外的形式,改为使用现代表达式语法。

因此,首先,在MainhWnd := WinExist()这一行。
我们想从当前活动的窗口开始,所以让我们像这样使用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%

在这里您无法获得子窗口的位置,因为它尚不存在。
如果gui是预先确定的,则只需知道其宽度即可。
而且,如果其宽度不变,则也不必每次移动窗口都获得其宽度。
因此,也许只需将预定宽度存储在变量中就可以了。

如果宽度不能预先确定,您可以例如快速显示gui并抓住其宽度,然后立即将其移动。

ChildWindowWidthHalf := 349/2

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

cX := mX + offset2
cY := mY

Gui,% "x" cX " y" cY

在这里您还可以看到我抛弃传统语法并切换到expression的第一次出现,例如,此处将字符串与变量连接起来:
ahk_id %MainhWnd%% "ahk_id " MainhWnd
基本上,单个%后跟一个空格,这会强制命令的参数对表达式进行求值,而不是期望使用遗留文本参数。
如果这一切对您来说都是全新的,我建议您查阅文档上的this页。


然后介绍HookProc函数中的问题:

HookProc(hWinEventHook,event,hwnd) 
{ 
    global   MainHwnd,ChildhWnd 
    if (hwnd = MainHwnd)
    {
        SetWinDelay,-1
        WinGetPos hX,hY,hW,hH,ahk_id %MainhWnd% 
        WinGetPos cX,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是当前活动的窗口,而不是脚本开始时确定的一个窗口。
还可以添加此变量ChildWindowWidthHalf

if (hwnd = MainHwnd)
此检查对我们也没有意义,应将其完全删除。
虽然,我强烈建议您使用此if (hwnd = WinActive("A"))替换它,以便您过滤掉与当前活动的窗口移动无关的消息。

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

对此:

WinGetPos hX,ahk_id %MainhWnd% 
WinGetPos cX,ahk_id %ChildhWnd% 
        
offset1 := (hw / 2) - (cw / 2)

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

然后进入WinMove command
WinMove ahk_id %ChildhWnd%,h%ch%
使用错误。 width和height参数的前面没有wh字符。无论如何,我们没有理由改变窗口的宽度或高度。它应该是这样的:
WinMove,% "ahk_id " ChildhWnd,% X,% Y


现在您应该有一个 working 脚本,如下所示:

SetWinDelay,-1

hookProcAdr := RegisterCallback("HookProc")
hHook := SetWinEventHook(0x800B,0x800B,hookProcAdr,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,Button,Button


MainhWnd := WinExist("A")

ChildWindowWidthHalf := 349/2

WinGetPos,% "x" cX " y" cY
return

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

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

但这只会在窗口移动时更新子窗口的位置,而不是在激活另一个窗口时更新。
因此,您还需要抓住一个窗口来获得焦点。
用一个外壳钩可以很好地做到这一点。
我将在代码中用注释来说明,这是相关的文档链接:
RegisterShellHookWindowRegisterWindowMessageOnMessage

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

现在您终于应该得到像这样的成品了:

SetWinDelay,Button

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

MainhWnd := WinExist("A")

ChildWindowWidthHalf := 349/2

WinGetPos,% "x" cX " y" cY
return

MsgMonitor(wParam,lParam)
}

HookProc(hWinEventHook,dwFlags) 
}