close windows on script exit/reload

Post your working scripts, libraries and tools
User avatar
jeeswg
Posts: 6547
Joined: 19 Dec 2016, 01:58
Location: UK

close windows on script exit/reload

23 Jun 2019, 08:15

Some code to defer closing a window until the script is exited/reloaded.
Do share any comments or notify of any other similar scripts. Thanks.

Code: Select all

q:: ;test defer window close (this adds a window to a list, to be closed on script exit/reload)
WinGet, hWnd, ID, ahk_class Notepad
WinCloseOnExit(hWnd)
return

;==================================================

WinCloseOnExit(hWnd)
{
	local
	global WinCloseOnExitClass
	static oWinCloseOnExit := new WinCloseOnExitClass
	WinGet, vPID, PID, % "ahk_id " hWnd
	oWinCloseOnExit[hWnd] := vPID
}

;==================================================

class WinCloseOnExitClass
{
	__Delete()
	{
		local
		vDHW := A_DetectHiddenWindows
		DetectHiddenWindows, On
		for hWnd, vPID in this
		{
			WinGet, vPID2, PID, % "ahk_id " hWnd
			if (vPID = vPID2)
				WinClose, % "ahk_id " hWnd
		}
		DetectHiddenWindows, % vDHW
	}
}

;==================================================
[EDIT:] Replaced 'DeferClose' with 'CloseOnExit'
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
need4speed
Posts: 73
Joined: 22 Apr 2016, 06:50

Re: close windows on script exit/reload

27 Jun 2019, 09:26

Why not use OnExit() [v1.1.20+] ?

Code: Select all

OnExit()
User avatar
jeeswg
Posts: 6547
Joined: 19 Dec 2016, 01:58
Location: UK

Re: close windows on script exit/reload

06 Jul 2019, 10:04

It's simpler to call WinCloseOnExit, than to store window information, and create/alter an OnExit callback function.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
iPhilip
Posts: 397
Joined: 02 Oct 2013, 12:21

Re: close windows on script exit/reload

07 Jul 2019, 02:10

@jeeswg, Thank you for sharing your WinCloseOnExit() function. I studied it and I don't think keeping track of the Process ID of the window is necessary. After all, window handles are unique. I also considered alternate ways to do the same thing and came up with the following three approaches:

The first one is a simplified version of what you posted:

Code: Select all

Loop, 2 {
   Run, Notepad, , , PID
   WinWaitActive, ahk_pid %PID%
   WinCloseOnExit(WinExist())
}
Return

Esc::ExitApp

;==================================================

WinCloseOnExit(hWnd)
{
	static oWinCloseOnExit := new WinCloseOnExitClass
	oWinCloseOnExit.hWnd.Push(hWnd)
}

;==================================================

class WinCloseOnExitClass
{
   __New()
   {
      this.hWnd := []
   }
   
	__Delete()
	{
      for each, hWnd in this.hWnd
         WinClose, % "ahk_id " hWnd
	}
}

;==================================================
The second approach is to combine the function and class into one function as follows:

Code: Select all

Loop, 2 {
   Run, Notepad, , , PID
   WinWaitActive, ahk_pid %PID%
   WinCloseOnExit(WinExist())
}
Return

Esc::ExitApp

;==================================================

WinCloseOnExit(hWnd) {
   static hWnds := [], _ := {base:{__Delete:Func("WinCloseOnExit").Bind("")}}
   if hWnd
      hWnds.Push(hWnd)
   else
      for each, hWnd in hWnds
         WinClose, % "ahk_id " hWnd
}

;==================================================
Finally, the advantage of using the OnExit function is that you can conditionally close the windows using the ExitReason and/or the ExitCode parameters. See the following implementation.

Code: Select all

Loop, 2 {
   Run, Notepad, , , PID
   WinWaitActive, ahk_pid %PID%
   WinCloseOnExit(WinExist())
}
Return

Esc::ExitApp

;==================================================

WinCloseOnExit(hWnd) {
   static hWnds := [], _ := OnExit(Func("ExitSub").Bind(hWnds))
   hWnds.Push(hWnd)
}

;==================================================

ExitSub(hWnds, ExitReason, ExitCode) {
   if (ExitReason = "Exit")
      for each, hWnd in hWnds
         WinClose, % "ahk_id " hWnd
}

;==================================================
I welcome your (or anyone else's) feedback.

Cheers!

- iPhilip
Windows 7 Pro (64 bit) - AutoHotkey v1.1+ (Unicode 32-bit)
Helgef
Posts: 3855
Joined: 17 Jul 2016, 01:02
Contact:

Re: close windows on script exit/reload

07 Jul 2019, 03:08

@jeeswg and @iPhilip thanks for sharing :thumbsup:.
Here is an example, combining some of your ideas,

Code: Select all

WinCloseOnExit(params*) {
	static hWnds := []
	local
	if params.length() == 1 { ; adding a hwnd
		if !hWnds.length()
			OnExit(a_thisfunc)
		return hWnds.Push(params[1])
	}
	; exiting...
	for each, hWnd in hWnds
         WinClose, % "ahk_id " hWnd
}
As far as I know both hwnds and pids can be reused by other windows / programs after they have been killed. So there is no actual guarantee that the hwnd / pid refers to the same window / program when the exit function is called. A more secure approach could use a hook to remove hwnds / pids from the list when they are closed.

Cheers.

Edit, for v2 I'd use this, if I didn't care to implement a hook that is,

Code: Select all

WinCloseOnExit(p*){
	onexit((*) => winclose(p*))
}
Edit 2: @iPhilip , note that your second approach relies on undocumented behaviour, that is, when the static var _ is released, there is no guarantee that the static var hWnds hasn't been released. You can bind the hWnds array to the __delete callback, eg,

Code: Select all

WinCloseOnExit(hWnd) {
   static hWnds := [], _ := {base:{__Delete:Func("WinCloseOnExit").Bind(hWnds)}}
   if !isobject(hWnd)
      hWnds.Push(hWnd)
   else
      for each, handle in hWnd
         WinClose, % "ahk_id " handle
}
User avatar
jeeswg
Posts: 6547
Joined: 19 Dec 2016, 01:58
Location: UK

Re: close windows on script exit/reload

07 Jul 2019, 08:07

- @iPhilip: Good code to set the base object, thanks. Because I don't know how exactly AHK v2 will handle base objects, and because it has more of a tutorial quality, I wrote a separate class instead of going for the cool hack.

- @iPhilip/Helgef: hWnds and PIDs can be reused. It seemed relatively safe to check that the hWnd still had the same PID, but I might like to add in further checks. Even if you used a hook to check for a window being closed, checking the PID might still be a good safety measure.
- Perhaps the best approach would be to store and check the PID's creation time. See ProcessGetCreationTime:
jeeswg's dates tutorial - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=65544
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
iPhilip
Posts: 397
Joined: 02 Oct 2013, 12:21

Re: close windows on script exit/reload

09 Jul 2019, 14:49

Helgef wrote:
07 Jul 2019, 03:08
@jeeswg and @iPhilip thanks for sharing :thumbsup:.
Here is an example, combining some of your ideas,

Code: Select all

WinCloseOnExit(params*) {
	static hWnds := []
	local
	if params.length() == 1 { ; adding a hwnd
		if !hWnds.length()
			OnExit(a_thisfunc)
		return hWnds.Push(params[1])
	}
	; exiting...
	for each, hWnd in hWnds
         WinClose, % "ahk_id " hWnd
}
@Helgef thank you for this function. I like how you combined things into a single function. I modified it slightly in the version below to make it more evident how to use Params* to conditionally close the windows.

Code: Select all

WinCloseOnExit(Params*) {
	static hWnds := []
	if (Params.Length() = 1) {  ; Params[1] = hWnd
		if !hWnds.Length()
			OnExit(A_ThisFunc)
		hWnds.Push(Params[1])
	} else if (Params[1] = "Exit")  ; Params[1] = ExitReason, Params[2] = ExitCode
      for each, hWnd in hWnds
         WinClose, % "ahk_id " hWnd
}
On the issue of hWnds and PIDs reusability ...
Helgef wrote:
07 Jul 2019, 03:08
As far as I know both hwnds and pids can be reused by other windows / programs after they have been killed. So there is no actual guarantee that the hwnd / pid refers to the same window / program when the exit function is called. A more secure approach could use a hook to remove hwnds / pids from the list when they are closed.
jeeswg wrote:
07 Jul 2019, 08:07
- @iPhilip/Helgef: hWnds and PIDs can be reused. It seemed relatively safe to check that the hWnd still had the same PID, but I might like to add in further checks. Even if you used a hook to check for a window being closed, checking the PID might still be a good safety measure.
- Perhaps the best approach would be to store and check the PID's creation time. See ProcessGetCreationTime:
jeeswg's dates tutorial - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=65544
Upon further reading, I agree with both of you that there is no guarantee that the hwnd/pid values will refer to the same window but I would argue that the hwnd values will be unique for every existing window. After all, window handles are pointers to a piece of allocated memory. Thus, following up on @Helgef suggestion, I added a hook to the WinCloseOnExitClass class to remove the hwnd values from the class instance when they are closed. I even added a method to manually remove a given hwnd value. See below:

Code: Select all

#NoEnv

Loop, 2 {
   Run, Notepad, , , PID
   WinWaitActive, ahk_pid %PID%
   WinCloseOnExit(WinExist())
}
Return

F3::WinCloseOnExit(WinExist("A"), "Remove")

Esc::ExitApp

;===================================================

WinCloseOnExit(hWnd, Action := "Add")
{
   static oWinCloseOnExit := new WinCloseOnExitClass
   
   if (Instr(Action, "A") = 1)
      oWinCloseOnExit.Add(hWnd)
   else
      oWinCloseOnExit.Remove(hWnd)
}

;===================================================

class WinCloseOnExitClass
{
   static _ := WinCloseOnExitClass.Init()
   
   Init()
   {
      this.hWnds := []
      DllCall("RegisterShellHookWindow", "Ptr", A_ScriptHwnd)
      this.MsgNum := DllCall("RegisterWindowMessage", "Str", "SHELLHOOK")
      OnMessage(this.MsgNum, ObjBindMethod(WinCloseOnExitClass, "ShellMessage"))
   }
     
   ShellMessage(wParam, lParam)
   {
      if (wParam = 2 && (Index := this.HasValue(lParam)))
         Return this.hWnds.RemoveAt(Index)
   }
   
   HasValue(hWin)
   {
      for Each, hWnd in this.hWnds
         if (hWnd = hWin)
            Return Each
   }
   
   Add(hWnd)
   {
      Return this.hWnds.Push(hWnd)
   }
   
   Remove(hWnd)
   {
      Return this.ShellMessage(2, hWnd)
   }
   
   __Delete()
   {
      OnMessage(this.MsgNum, "")
      for Each, hWnd in this.hWnds
         WinClose, % "ahk_id " hWnd
   }
}
On the v2 version:
Helgef wrote:
07 Jul 2019, 03:08
Edit, for v2 I'd use this, if I didn't care to implement a hook that is,

Code: Select all

WinCloseOnExit(p*){
	onexit((*) => winclose(p*))
}
I love how simple and elegant that looks! I tested it with v2 but it didn't work :(. The version below does (just as elegant :) ):

Code: Select all

WinCloseOnExit(p*){
   OnExit((*) => WinClose("ahk_id " p[1]))
}
Finally,
Helgef wrote:
07 Jul 2019, 03:08
Edit 2: @iPhilip , note that your second approach relies on undocumented behaviour, that is, when the static var _ is released, there is no guarantee that the static var hWnds hasn't been released. You can bind the hWnds array to the __delete callback, eg,

Code: Select all

WinCloseOnExit(hWnd) {
   static hWnds := [], _ := {base:{__Delete:Func("WinCloseOnExit").Bind(hWnds)}}
   if !isobject(hWnd)
      hWnds.Push(hWnd)
   else
      for each, handle in hWnd
         WinClose, % "ahk_id " handle
}
Thank you. I was unsure about my approach (though I did test it). I appreciate your suggestion as I don't like to rely on undocumented behavior.

Edit: Corrected WinCloseOnExitClass class.
Last edited by iPhilip on 10 Jul 2019, 11:08, edited 1 time in total.
Windows 7 Pro (64 bit) - AutoHotkey v1.1+ (Unicode 32-bit)
Helgef
Posts: 3855
Joined: 17 Jul 2016, 01:02
Contact:

Re: close windows on script exit/reload

10 Jul 2019, 00:27

I tested it with v2 but it didn't work :(
It works more generally by allowing you to pass all winclose criteria, so if you want to close by hwnd, you pass 'ahk_id' hwnd.

Cheers.
iPhilip
Posts: 397
Joined: 02 Oct 2013, 12:21

Re: close windows on script exit/reload

10 Jul 2019, 01:53

Helgef wrote:
10 Jul 2019, 00:27
I tested it with v2 but it didn't work :(
It works more generally by allowing you to pass all winclose criteria, so if you want to close by hwnd, you pass 'ahk_id' hwnd.

Cheers.
Got it. Thank you.
Windows 7 Pro (64 bit) - AutoHotkey v1.1+ (Unicode 32-bit)

Return to “Scripts and Functions”

Who is online

Users browsing this forum: robodesign and 54 guests