[alpha] WinEvent - easily detect window open, close, move, and more

Post your working scripts, libraries and tools.
Descolada
Posts: 1141
Joined: 23 Dec 2021, 02:30

[alpha] WinEvent - easily detect window open, close, move, and more

05 Mar 2024, 15:40

This library was inspired by HotKeyIt's WinWaitCreated() function. I haven't yet tested it thorougly, so feel free to report any bugs...

The library is available on GitHub

The WinEvent class can monitor window events for all windows, or for specific windows. Currently the following events are supported: Show, Create, Close, Active, NotActive, Move, MoveStart, MoveEnd, Minimize, Restore, Maximize. See comments for the functions in the library for more information.

Some examples

Example 1
Waits for a new Notepad window to be created and shown. This could also be achieved using WinEvent.Create, but along with the Notepad main window there are some other hidden windows created as well that match "ahk_exe notepad.exe" which we don't want to capture. In the case of Notepad we could use "ahk_class Notepad ahk_exe notepad.exe" to filter for the main window, but that method isn't generalizable, so WinEvent.Show is a safer option.

Code: Select all

#Requires AutoHotkey v2
#include WinEvent.ahk

; Detects when a Notepad window is created. Press F1 to run Notepad and test.
WinEvent.Show(NotepadCreated, "ahk_class Notepad ahk_exe notepad.exe")
Persistent()

NotepadCreated(hook, hWnd, dwmsEventTime) {
    ToolTip "Notepad was created at " dwmsEventTime ", hWnd " hWnd "`n"
    SetTimer ToolTip, -3000
}

F1::Run("notepad.exe")
Example 2
Detects the closing of the newly created Notepad window. Note that using "A" instead of WinExist("A") would detect the closing of any active window, not Notepad. The third argument 1 means that the hook is stopped once the callback function has been called once.

Code: Select all

#Requires AutoHotkey v2
#include WinEvent.ahk

Run "notepad.exe"
WinWaitActive "ahk_exe notepad.exe"
; Close Notepad to activate
WinEvent.Close(ActiveWindowClosed, WinExist("A"), 1)
Persistent()

ActiveWindowClosed(*) {
    MsgBox "Notepad window closed, press OK to exit"
    ExitApp
}
Example 3
Detects when any window is maximized.

Code: Select all

#Requires AutoHotkey v2
#include WinEvent.ahk

; Detects when any window is maximized
WinEvent.Maximize(WindowMaximizedEvent)
Persistent()

WindowMaximizedEvent(hook, hWnd, dwmsEventTime) {
    if MsgBox("A window was maximized at " dwmsEventTime ", hWnd " hWnd "`n`nStop hook?",, 0x4) = "Yes"
        hook.Stop()
}

F1::Run("notepad.exe")
Example 4
Detects when the active window changes and displays info about the activated window.

Code: Select all

#Requires AutoHotkey v2
#include WinEvent.ahk

WinEvent.Active(ActiveWindowChanged)
Persistent()

ActiveWindowChanged(hook, hWnd, *) {
    ToolTip "Active window changed! New window info: `n" WinGetInfo(hWnd)
    SetTimer ToolTip, -5000
}

/**
 * Gets info about a window (title, process name, location etc)
 * @param WinTitle Same as AHK WinTitle
 * @param {number} Verbose How verbose the output should be (default is 1):
 *  0: Returns window title, hWnd, class, process name, PID, process path, screen position, min-max info, styles and ex-styles
 *  1: Additionally returns TransColor, transparency level, text (both hidden and not), statusbar text
 *  2: Additionally returns ClassNN names for all controls
 * @param WinText Same as AHK WinText
 * @param ExcludeTitle Same as AHK ExcludeTitle
 * @param ExcludeText Same as AHK ExcludeText
 * @param {string} Separator Linebreak character(s)
 * @returns {string} The info as a string. 
 * @example MsgBox(WinGetInfo("ahk_exe notepad.exe", 2))
 */
WinGetInfo(WinTitle:="", Verbose := 1, WinText:="", ExcludeTitle:="", ExcludeText:="", Separator := "`n") {
    if !(hWnd := WinExist(WinTitle, WinText, ExcludeTitle, ExcludeText))
        throw TargetError("Target window not found!", -1)
    out := 'Title: '
    try out .= '"' WinGetTitle(hWnd) '"' Separator
    catch
        out .= "#ERROR" Separator
    out .=  'ahk_id ' hWnd Separator
    out .= 'ahk_class '
    try out .= WinGetClass(hWnd) Separator
    catch
        out .= "#ERROR" Separator
    out .= 'ahk_exe '
    try out .= WinGetProcessName(hWnd) Separator
    catch
        out .= "#ERROR" Separator
    out .= 'ahk_pid '
    try out .= WinGetPID(hWnd) Separator
    catch
        out .= "#ERROR" Separator
    out .= 'ProcessPath: '
    try out .= '"' WinGetProcessPath(hWnd) '"' Separator
    catch
        out .= "#ERROR" Separator
    out .= 'Screen position: '
    try { 
        WinGetPos(&X, &Y, &W, &H, hWnd)
        out .= "x: " X " y: " Y " w: " W " h: " H Separator
    } catch
        out .= "#ERROR" Separator
    out .= 'MinMax: '
    try out .= ((minmax := WinGetMinMax(hWnd)) = 1 ? "maximized" : minmax = -1 ? "minimized" : "normal") Separator
    catch
        out .= "#ERROR" Separator

    static Styles := Map("WS_OVERLAPPED", 0x00000000, "WS_POPUP", 0x80000000, "WS_CHILD", 0x40000000, "WS_MINIMIZE", 0x20000000, "WS_VISIBLE", 0x10000000, "WS_DISABLED", 0x08000000, "WS_CLIPSIBLINGS", 0x04000000, "WS_CLIPCHILDREN", 0x02000000, "WS_MAXIMIZE", 0x01000000, "WS_CAPTION", 0x00C00000, "WS_BORDER", 0x00800000, "WS_DLGFRAME", 0x00400000, "WS_VSCROLL", 0x00200000, "WS_HSCROLL", 0x00100000, "WS_SYSMENU", 0x00080000, "WS_THICKFRAME", 0x00040000, "WS_GROUP", 0x00020000, "WS_TABSTOP", 0x00010000, "WS_MINIMIZEBOX", 0x00020000, "WS_MAXIMIZEBOX", 0x00010000, "WS_TILED", 0x00000000, "WS_ICONIC", 0x20000000, "WS_SIZEBOX", 0x00040000, "WS_OVERLAPPEDWINDOW", 0x00CF0000, "WS_POPUPWINDOW", 0x80880000, "WS_CHILDWINDOW", 0x40000000, "WS_TILEDWINDOW", 0x00CF0000, "WS_ACTIVECAPTION", 0x00000001, "WS_GT", 0x00030000)
    , ExStyles := Map("WS_EX_DLGMODALFRAME", 0x00000001, "WS_EX_NOPARENTNOTIFY", 0x00000004, "WS_EX_TOPMOST", 0x00000008, "WS_EX_ACCEPTFILES", 0x00000010, "WS_EX_TRANSPARENT", 0x00000020, "WS_EX_MDICHILD", 0x00000040, "WS_EX_TOOLWINDOW", 0x00000080, "WS_EX_WINDOWEDGE", 0x00000100, "WS_EX_CLIENTEDGE", 0x00000200, "WS_EX_CONTEXTHELP", 0x00000400, "WS_EX_RIGHT", 0x00001000, "WS_EX_LEFT", 0x00000000, "WS_EX_RTLREADING", 0x00002000, "WS_EX_LTRREADING", 0x00000000, "WS_EX_LEFTSCROLLBAR", 0x00004000, "WS_EX_CONTROLPARENT", 0x00010000, "WS_EX_STATICEDGE", 0x00020000, "WS_EX_APPWINDOW", 0x00040000, "WS_EX_OVERLAPPEDWINDOW", 0x00000300, "WS_EX_PALETTEWINDOW", 0x00000188, "WS_EX_LAYERED", 0x00080000, "WS_EX_NOINHERITLAYOUT", 0x00100000, "WS_EX_NOREDIRECTIONBITMAP", 0x00200000, "WS_EX_LAYOUTRTL", 0x00400000, "WS_EX_COMPOSITED", 0x02000000, "WS_EX_NOACTIVATE", 0x08000000)
    out .= 'Style: '
    try {
        out .= (style := WinGetStyle(hWnd)) " ("
        for k, v in Styles {
            if v && style & v {
                out .= k " | "
                style &= ~v
            }
        }
        out := RTrim(out, " |")
        if style
            out .= (SubStr(out, -1, 1) = "(" ? "" : ", ") "Unknown enum: " style
        out .= ")" Separator
    } catch
        out .= "#ERROR" Separator

        out .= 'ExStyle: '
        try {
            out .= (style := WinGetExStyle(hWnd)) " ("
            for k, v in ExStyles {
                if v && style & v {
                    out .= k " | "
                    style &= ~v
                }
            }
            out := RTrim(out, " |")
            if style
                out .= (SubStr(out, -1, 1) = "(" ? "" : ", ") "Unknown enum: " style
            out .= ")" Separator
        } catch
            out .= "#ERROR" Separator

    
    if Verbose {
        out .= 'TransColor: '
        try out .= WinGetTransColor(hWnd) Separator
        catch
            out .= "#ERROR" Separator
        out .= 'Transparent: '
        try out .= WinGetTransparent(hWnd) Separator
        catch
            out .= "#ERROR" Separator

        PrevDHW := DetectHiddenText(0)
        out .= 'Text (DetectHiddenText Off): '
        try out .= '"' WinGetText(hWnd) '"' Separator
        catch
            out .= "#ERROR" Separator
        DetectHiddenText(1)
        out .= 'Text (DetectHiddenText On): '
        try out .= '"' WinGetText(hWnd) '"' Separator
        catch
            out .= "#ERROR" Separator
        DetectHiddenText(PrevDHW)

        out .= 'StatusBar Text: '
        try out .= '"' StatusBarGetText(1, hWnd) '"' Separator
        catch
            out .= "#ERROR" Separator
    }
    if Verbose > 1 {
        out .= 'Controls (ClassNN): ' Separator
        try {
            for ctrl in WinGetControls(hWnd)
                out .= '`t' ctrl Separator
        } catch
            out .= "#ERROR" Separator
    }
    return SubStr(out, 1, -StrLen(Separator))
}
Version history
05.03.24. First post.
06.03.24. Added the hook object as an argument to the callback function. Added Example 4.
24.03.24. Added WinEvent.Pause(), WinEvent.IsPaused, EventHook.Pause(), EventHook.IsPaused. Renamed WinEvent.IsActive to WinEvent.IsRegistered and WinEvent.IsEventTypeActive to WinEvent.IsEventTypeRegistered (WinEvent wouldn't be active if paused, so IsRegistered is a more descriptive). Fixed issue where ahk_id couldn't be reliably used. Added the option of using an object with the hWnd property as WinTitle.
Last edited by Descolada on 24 Mar 2024, 06:35, edited 2 times in total.
Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Re: [alpha] WinEvent - easily detect window open, close, move, and more

20 Mar 2024, 16:37

@Descolada This is great!! Very intuitive usage!! Love it.
jschwalbe
Posts: 38
Joined: 11 Dec 2015, 10:38

Re: [alpha] WinEvent - easily detect window open, close, move, and more

22 Mar 2024, 15:31

Hello! Thanks for this. I stumbled upon this today and already put it to use a few different ways, and just realized how new it is!

I will have to experiment what exactly is going on, but I've found a tiny bug. My script is 2200 lines, so I won't paste it all. But essentially I am doing the following:

Code: Select all

F4::
{
				MouseMove 1000,500
				Send "^{Home}"
				Send "{Click , , Right}"
				Send "f"
				Send "{Tab}+{Tab}" 
				Send "****{Enter}"
}
[Mod edit: Added [code][/code] tags. Please use them yourself when posting code!]

And this works great if I don't have the WinEvent.ahk library loaded and running something like WinEvent.Show(NotepadCreated, "ahk_class Notepad ahk_exe notepad.exe")

When I add that line in, then it starts to slow down significantly such that what normally was imperceptible now takes up to 2 seconds.
What makes this stranger is that if I have Window Spy open, it is once again imperceptible (until I close Window Spy)..

I'll say, I tried putting the above code into it's own ahk file and running it, and it didn't go slow. So I'm clearly causing the problem with all my code, but it's still strange how Window Spy causes a difference in behavior simply by being open/closed.
Thoughts? Thanks!
Descolada
Posts: 1141
Joined: 23 Dec 2021, 02:30

Re: [alpha] WinEvent - easily detect window open, close, move, and more

23 Mar 2024, 00:28

@jschwalbe I'm not sure what the problem might be: as you said your example code worked fine with WinEvent.ahk loaded. Perhaps if there was a reproducible example I could try figuring it out?
eugenesv
Posts: 175
Joined: 21 Dec 2015, 10:11

Re: [alpha] WinEvent - easily detect window open, close, move, and more

23 Mar 2024, 02:33

As far as I understand, suspending hotkeys/pausing the script has no effect on the WinEven lib callbacks. There is an easy workaround - check for A_IsSuspended and do nothing in your callback, but I was wondering whether you think it might make sense for the library to have this option at the time of registering the callback (or maybe even making it the default, though not sure whether it's the best default)
Descolada
Posts: 1141
Joined: 23 Dec 2021, 02:30

Re: [alpha] WinEvent - easily detect window open, close, move, and more

23 Mar 2024, 07:14

@eugenesv it doesn't make much sense because Suspend applies to hotkeys and hotstrings (WinEvents are neither); and Pause pauses the current thread, whereas WinEvents are launched via SetTimer as new threads. If you wish you can use Thread "NoTimers" to disable the callbacks for the duration of the running thread.
eugenesv
Posts: 175
Joined: 21 Dec 2015, 10:11

Re: [alpha] WinEvent - easily detect window open, close, move, and more

23 Mar 2024, 07:37

Well, you described the reasons why callbacks can't be stopped the same way you stop other script activity. My point was that it might be unexpected: if I stop all hotkeys and hotstrings/pause, which is mentally "stop AHK without exiting", it's unexpected that callbacks continue to work

(couldn't make NoTimers work, the callbacks still worked (and if I understand it correctly, I wouldn't want to permanently disable them, but only when I click suspend/pause in the script's icon), so I'll just continue to use the IsSuspended check)
Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Re: [alpha] WinEvent - easily detect window open, close, move, and more

23 Mar 2024, 09:42

Hi @Descolada

Does the winTitle parameter only support the windows title but not the hwnd? It seems like it:

Code: Select all


WinEvent.Active(UpdateDisplayedData, "Hanging-Manager")
WinEvent.Active(UpdateDisplayedData2, HM_Gui.Hwnd)

UpdateDisplayedData(*) {
	ToolTip("Activated1", 10,10)
}
UpdateDisplayedData2(*) {
	ToolTip("Activated2",50,50, 2)
}

}
This code only shows "Activated1"
Descolada
Posts: 1141
Joined: 23 Dec 2021, 02:30

Re: [alpha] WinEvent - easily detect window open, close, move, and more

24 Mar 2024, 06:32

@eugenesv I've added WinEvent.Pause(), WinEvent.IsPaused, EventHook.Pause() and EventHook.IsPaused, so now you can use something like #p::WinEvent.Pause(-1), Pause(-1) ; Win+P.

@Spitzi the latest update should have fixed that issue.
Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Re: [alpha] WinEvent - easily detect window open, close, move, and more

25 Mar 2024, 08:08

Yes! Works fine now, @Descolada. Thanks. VERY usefull.
jschwalbe
Posts: 38
Joined: 11 Dec 2015, 10:38

Re: [alpha] WinEvent - easily detect window open, close, move, and more

25 Mar 2024, 14:58

Descolada wrote:
23 Mar 2024, 00:28
@jschwalbe I'm not sure what the problem might be: as you said your example code worked fine with WinEvent.ahk loaded. Perhaps if there was a reproducible example I could try figuring it out?
Ok I've got one that shows what I'm talking about. Here's the script. comment/uncomment the WinEvent line.
When it's uncommented, the Notepad window types the keys in a jerky fashion.... for example, it will print 5 lines, then pause for 300 ms or so, and then print another 10 lines, pause 300 ms, etc.
When the WinEvent line is commented out, it's perfectly smooth without pauses.


Code:
Spoiler
It's almost as if WinEvent is causing the entire script to momentarily grind to a halt every x seconds while it updates things..?
Descolada
Posts: 1141
Joined: 23 Dec 2021, 02:30

Re: [alpha] WinEvent - easily detect window open, close, move, and more

26 Mar 2024, 11:25

@jschwalbe, I've pushed an update to the library, please see if that fixed the problem.
Btw, are you running Windows 10, and did the halting happen only in Notepad? Did you manage to replicate it in other programs than Notepad?
jschwalbe
Posts: 38
Joined: 11 Dec 2015, 10:38

Re: [alpha] WinEvent - easily detect window open, close, move, and more

26 Mar 2024, 13:38

Descolada wrote:
26 Mar 2024, 11:25
@jschwalbe, I've pushed an update to the library, please see if that fixed the problem.
Btw, are you running Windows 10, and did the halting happen only in Notepad? Did you manage to replicate it in other programs than Notepad?
Yes Win 10. It was actually a more complex problem with delays causing a context menu to misfire and the wrong item to be picked from the list. Working on improving an ancient electronic medical record. I just made it show up in Notepad since I figured everyone has access to *that* :)

That seems to have done the trick! Crazy to see the diff from one version to the next was all it took to fix it.
Thanks!

Return to “Scripts and Functions (v2)”

Who is online

Users browsing this forum: DuyMinh and 79 guests