Hotkey to toggle window size Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
Lem2001
Posts: 127
Joined: 27 Jun 2017, 17:59

Hotkey to toggle window size

20 Oct 2020, 23:24

I'm want to make hotkey to toggle a change in window size (for one specific program, not all windows). I didn't know how to retain a toggle state, but I found some code which works well:

//autohotkey.com/board/topic/68710-autohotkey-toggle-state-function/?p=434956

Code: Select all

n=0   ; this isn't really needed, the invert of empty is 1
return
f1::
n:=!n         ; invert or reverse the prior value, 1 or 0 are the only choices
if n=1
  tooltip, this is true
else
  tooltip, this is false
return
I therefore just needed to insert my window sizes in place of the tooltip messages. However, I don't want to used fixed sizes, I want the size to be relative to the current window size.

So I used WinGetPos, X, Y, Width, Height, A to store the current window settings and then I want to first alter the X coordinate by -27 pixels and the width value by +27 pixels, then on the subsequent toggle state, reverse that (i.e. X coordinate +27 pixels and width -27 pixels).

This is where I got into trouble (because I can't use expressions), but I tried the following:

Code: Select all

n=0   			
return
f1::
WinGetPos, X, Y, Width, Height, A
n:=!n 			; invert or reverse the prior value, 1 or 0 are the only choices
if n=1
  WinMove, A ,, (%X% - 27), , (%Width% + 27), 
else
  WinMove, A ,, (%X% + 27), , (%Width% - 27), 
return
This doesn't work (the hotkey does nothing). What am I doing wrong?

Also, as far as I understand, this routine always starts in the same state, but will it go out of sync if I start using other windows from the same program? Presumably I you could use window ID instead of class or exe name and have the routine start with from its default state with each new window instance, but I don;t know how to do that.

The purpose of this is to have my text editor window content area remain constant in size when I toggle line numbering on and off (which takes up 27 pixels of space on the left hand side) therefore it's important the the resizing stays in sync and doesn't end up reducing the window with when the line numbering column is added (and vice versa). Unfortunately there's no way to detect the line number toggle status (no visible toolbar button, accessible menu, or even registry key to determine the line number toggle state) so the only thing I have going for me is the fact that line numbering always defaults to off when a new window is opened.So this should give a consistent starting point.

Would appreciate some help getting a working script.
User avatar
Jim Dunn
Posts: 478
Joined: 01 Sep 2020, 20:21
Location: NSW

Re: Hotkey to toggle window size

21 Oct 2020, 01:18

Try:

Code: Select all

n=0   			
return
f1::
WinGetPos, X, Y, Width, Height, A
n:=!n 			; invert or reverse the prior value, 1 or 0 are the only choices
if n=1
  WinMove, A ,, X - 27, , Width + 27
else
  WinMove, A ,, X + 27, , Width - 27
return
Seems to work when I test.

Winmove is a little unusual, for an ahk parameter based command, in that its X, Y, Width, Height parameters accept expressions by default so you don't need the %s.
It does make sense for expressions to be expected there, but when you are accustomed to needing to force expressions in command parameters, or use enclosing %s, it can take you by surprise... ;)

On testing, it seems you can use % in those parameters to force an expression too, or at least doing so doesn't break anything, so

Code: Select all

n=0   			
return
f1::
WinGetPos, X, Y, Width, Height, A
n:=!n 			; invert or reverse the prior value, 1 or 0 are the only choices
if n=1
  WinMove, A ,, % X - 27, , % Width + 27
else
  WinMove, A ,, % X + 27, , % Width - 27
return
also works,

but your (%Width% + 27) etc. doesn't evaluate to a number:

Try:

Code: Select all

Width := 100

Msgbox % Width + 27     ; 127
Msgbox % %Width% + 27   ; empty

Msgbox % (Width + 27)   ; 127
Msgbox % (%Width% + 27) ; empty

Msgbox Width + 27		; Width + 27
Msgbox %Width% + 27		; 100 + 27

Msgbox (Width + 27)   	; (Width + 27)
Msgbox (%Width% + 27)   ; (100 + 27)
User avatar
Jim Dunn
Posts: 478
Joined: 01 Sep 2020, 20:21
Location: NSW

Re: Hotkey to toggle window size

21 Oct 2020, 02:44

Lem2001 wrote:
20 Oct 2020, 23:24
Also, as far as I understand, this routine always starts in the same state, but will it go out of sync if I start using other windows from the same program? Presumably I you could use window ID instead of class or exe name and have the routine start with from its default state with each new window instance, but I don;t know how to do that.
This is (mostly) untested, but it's an idea about how you might store and retrieve previously set toggle states in an array

Code: Select all

ToggleStates := {} ; array to store toggle states
Return

f1::
WinGet, thisHwnd, ID, A ; get the active window ID
n := !ToggleStates[thisHwnd] ; if ID not in array, 'n' will become 1, otherwise opposite of stored state (0 or 1)
ToggleStates[thisHwnd] := n ; store the new state for thisHwnd (ID)
Hw := "ahk_id " . thisHwnd ; make sure all subsequent commands act on the same window ID

WinGetPos, X, Y, Width, Height, % Hw
If (n = 1)
  WinMove, % Hw, , X - 27, , Width + 27 
Else
  WinMove, % Hw, , X + 27, , Width - 27
Return
It will 'forget' any stored states if the script exits or is reloaded, because the array only exists in memory while the script is running.

:!: Note that at the moment this will act on any window - if you want to restrict it to a certain class, exe or title then it would be easy to make it context-sensitive with #IfWinActive etc. (if we knew the criteria ;) )

If you left it running for a very long time, the array could get quite large, since every time you use F1 on a "new" window id, the array will get a new entry, and old ones are never removed.
Also, in the long-term, Windows will eventually re-use window handles, so they are not unique forever...

You could either regularly (every few days) reload the script, or else a way of "pruning" the array might be needed.
You could add a timestamp with every new entry, and remove items older than a certain age each time the hotkey ran - but that's probably overkill for a simple script.
You could even perhaps use a hook with this WinHook Class to remove entries for relevant windows that get closed, but that's probably getting really too elaborate for such a simple script...

If it were me, for simplicity, I'd probably just add a Timer to empty the array completely at something like 3 am each day, on the assumption that "yesterday's" saved states will all be stale and of no use.

But before worrying about any of that you'd need to see if it actually does what you want it to. ;)
Lem2001
Posts: 127
Joined: 27 Jun 2017, 17:59

Re: Hotkey to toggle window size

22 Oct 2020, 17:39

Thank you very much for your detailed and helpful reply. The first toggle part works fine (now that I removed the %). Restricting this action to only the program in question is something that even I can manage to do, so we're good on that score too, thanks.

However, with regards to the "getting out of sync issue" that was more challenging for me to understand. I've carefully studied what you've written and the array method that you describe does seems like a workable solution (caveats aside about the increasing size of the array). The timer approach sounds like the simplest way to clean up, but rather than have it run at set specific times (I tend to use the computer at all hours of the day or night, so this is bound to be an issue) would it be possible instead to have the clean up happen when there are no instances of the program open (which usually happens at few times a day). Presumably once all windows of that particular program are closed, there's no use for the previous window handles anyway (if I've understood things correctly).
User avatar
Jim Dunn
Posts: 478
Joined: 01 Sep 2020, 20:21
Location: NSW

Re: Hotkey to toggle window size

22 Oct 2020, 17:53

Yes, running a timer to check every few minutes whether there are any instances of the target program running, and emptying the array if there are not, would work in that scenario, and is actually a better solution.

If you test the example I gave above (it will run as a standalone script), in situations where multiple target windows are opened and closed etc, to make sure it is doing what you want, and provide the title/class/exe of your program I could easily add that, if you like.

Clearing (emptying) the array is simply a matter of issuing:

Code: Select all

ToggleStates := {}
when there are no instances of the target program running.

here's that original example with a 'debugging' section, in case it helps you test/see what's happening:

Code: Select all

ToggleStates := {} ; array to store toggle states
Return

f1::
WinGet, thisHwnd, ID, A ; get the active window ID
n := !ToggleStates[thisHwnd] ; if ID not in array, 'n' will become 1, otherwise opposite of stored state (0 or 1)
ToggleStates[thisHwnd] := n ; store the new state for thisHwnd (ID)
Hw := "ahk_id " . thisHwnd ; make sure all subsequent commands act on the same window ID

WinGetPos, X, Y, Width, Height, % Hw
If (n = 1)
  WinMove, % Hw, , X - 27, , Width + 27 
Else
  WinMove, % Hw, , X + 27, , Width - 27
  
; Debugging
c := 0
For k, v in ToggleStates
	c++
Msgbox % "Hwnd: " . thisHwnd . "   State: " . n . "`n`n"
	   . "Currently " . c . " stored state(s) in the array."

Return
Lem2001
Posts: 127
Joined: 27 Jun 2017, 17:59

Re: Hotkey to toggle window size

22 Oct 2020, 19:23

I just wrote out a post explaining that it seems to be working great, but it turns out that I was mistaken.

In your last code example, you left out the n=0 (maybe that's just because you were only focussing on the actual hotkey code). Is it not needed now because the array is keeping track compared to the old method? Presumably there stills needs to be a consistent starting state, so I left it in. Please let me know if it should be removed (I don't want to second guess things). I did test without it, just in case, but it still didn't work correctly.

With this new code, the window just keeps getting bigger on every key press.

Below is the code sample that I'm using. It's a straight copy of your code with a declaration added and the hotkey for toggle the line numbering.

Code: Select all

#If (WinActive("ahk_exe codepad.exe") && !WinActive("ahk_class #32770"))	
n=0																			
return

F9::
WinGet, thisHwnd, ID, A ; get the active window ID
n := !ToggleStates[thisHwnd] ; if ID not in array, 'n' will become 1, otherwise opposite of stored state (0 or 1)
ToggleStates[thisHwnd] := n ; store the new state for thisHwnd (ID)
Hw := "ahk_id " . thisHwnd ; make sure all subsequent commands act on the same window ID

WinGetPos, X, Y, Width, Height, % Hw
SendInput, ^+n ; toggle line numbering
If (n = 1)
  WinMove, % Hw, , X - 27, , Width + 27 
Else
  WinMove, % Hw, , X + 27, , Width - 27
Return
#If
User avatar
Jim Dunn
Posts: 478
Joined: 01 Sep 2020, 20:21
Location: NSW

Re: Hotkey to toggle window size

22 Oct 2020, 19:36

I saw the post arrive, but it was gone before i read it... :)

You don't need to initialise n := 0 because "new" windows (IDs not in the array) will return nothing when the array is checked, which equates to 0 (false) here (and then gets 'toggled' by !):

Code: Select all

n := !ToggleStates[thisHwnd] ; if ID not in array, 'n' will become 1, otherwise opposite of stored state (0 or 1)
You do, however, need the array declaration ToggleStates := {} which you removed. ;)
Otherwise ToggleStates is not treated as an array, so states are not stored and always end up appearing "the same".

Your #If only needs to (and should, really, only) enclose the hotkey:

Code: Select all

ToggleStates := {}																		
return

#If (WinActive("ahk_exe codepad.exe") && !WinActive("ahk_class #32770"))	
F9::
WinGet, thisHwnd, ID, A ; get the active window ID
n := !ToggleStates[thisHwnd] ; if ID not in array, 'n' will become 1, otherwise opposite of stored state (0 or 1)
ToggleStates[thisHwnd] := n ; store the new state for thisHwnd (ID)
Hw := "ahk_id " . thisHwnd ; make sure all subsequent commands act on the same window ID

WinGetPos, X, Y, Width, Height, % Hw
SendInput, ^+n ; toggle line numbering
If (n = 1)
  WinMove, % Hw, , X - 27, , Width + 27 
Else
  WinMove, % Hw, , X + 27, , Width - 27
Return
#If
I assume with:
!WinActive("ahk_class #32770")
you're deliberately checking the window is NOT that class...

Edit: Fixed dumb typo. :oops:
Last edited by Jim Dunn on 23 Oct 2020, 17:17, edited 7 times in total.
Lem2001
Posts: 127
Joined: 27 Jun 2017, 17:59

Re: Hotkey to toggle window size

22 Oct 2020, 19:41

Jim Dunn wrote:
22 Oct 2020, 19:36
You don't need n := 0 because new windows will return "false" when the array is checked, which equates.
You do, however, need the array declaration ToggleStates : = {} which you removed. ;)
My amazing coding skills are on full display :lol:

Let me try again. I'll get back to you.
User avatar
Jim Dunn
Posts: 478
Joined: 01 Sep 2020, 20:21
Location: NSW

Re: Hotkey to toggle window size

22 Oct 2020, 19:44

Yeah, well I'm no better... ;)
Should be ToggleStates := {} !!!

I added a dumb space in what you quote above (typo) :oops:

Incidentally, I've tested this myself with Notepad (and the 'debugging' code I showed above) and it seems, to me, to be doing what it's supposed to (except that your line number toggle Sendinput just gives me a satisfying "ding" because Notepad doesn't understand it... :) )
Lem2001
Posts: 127
Joined: 27 Jun 2017, 17:59

Re: Hotkey to toggle window size

22 Oct 2020, 20:07

OK, it's finally working!

I have multiple declarations in my hotkey script for various different programs. I literally put the ToggleStates := {} outside of the declaration for ahk_exe codepad.exe (above it), but it still wasn't working. So then I moved it right up to the very start of the script and it worked.

So back to my previous comment which I deleted.

I would like to take you up on your kind offer to write the timer code for me. Clearing the array should be easy enough (I think). I would have tried using something like:

Code: Select all

If !WinExist("ahk_exe codepad.exe")
{
 ToggleStates := {}
}
But I wouldn't have a clue how to write the timer part (or where to put it for that matter). I suppose it has to be persistent in order to function outside of the application's declaration.

By the way, just out of interest, seeing as the timer function is pretty basic (just emptying the array) would this work if run from a different script to the one that runs my hotkeys (which gets restarted all time because I'm always adding new hotkeys, tinkering, fixing things I've broken etc.) whereas my other script that has some timer-based stuff just sits there in memory.
User avatar
Jim Dunn
Posts: 478
Joined: 01 Sep 2020, 20:21
Location: NSW

Re: Hotkey to toggle window size  Topic is solved

22 Oct 2020, 20:31

Try:

Code: Select all

ToggleStates := {}	
SetTimer, ConditionallyClearArray, 600000    ; 10 * 60 * 1000 = will run every 10 minutes, persistently
Return

ConditionallyClearArray() {
	global ToggleStates
	If !(WinExist("ahk_exe codepad.exe")) {
		ToggleStates := {}
	}
}

#If (WinActive("ahk_exe codepad.exe") && !WinActive("ahk_class #32770"))	
F9::
WinGet, thisHwnd, ID, A ; get the active window ID
n := !ToggleStates[thisHwnd] ; if ID not in array, 'n' will become 1, otherwise opposite of stored state (0 or 1)
ToggleStates[thisHwnd] := n ; store the new state for thisHwnd (ID)
Hw := "ahk_id " . thisHwnd ; make sure all subsequent commands act on the same window ID

WinGetPos, X, Y, Width, Height, % Hw
SendInput, ^+n ; toggle line numbering
If (n = 1)
  WinMove, % Hw, , X - 27, , Width + 27 
Else
  WinMove, % Hw, , X + 27, , Width - 27
Return
#If

f12::
c := 0
For k, v in ToggleStates
	c++
Msgbox %  "Currently " . c . " stored state(s) in the array."
Return
Put the SetTimer line at the start, like you did with the ToggleStates := {} declaration - but probably best after that declaration, because it indirectly refers to it. The function can go pretty much anywhere.

I added an F12 hotkey so that you can 'peek' at the array count. You don't need that - but it might help you so that you can leave codepad closed for 10 minutes and see if the array got emptied.

The array only exists in this script - so you can't directly reset it from another script. There are various ways to get one script to communicate with another, like saving in an ini/textfile on disk instead of an array or sending windows messages between scripts if you're massively keen on doing that - each method would add some complexity (and using disk instead of memory is marginally slower).
Last edited by Jim Dunn on 22 Oct 2020, 21:25, edited 2 times in total.
User avatar
Jim Dunn
Posts: 478
Joined: 01 Sep 2020, 20:21
Location: NSW

Re: Hotkey to toggle window size

22 Oct 2020, 20:51

Oops - If you're using the first version I posted in the post directly above it won't work - I neglected to include global ToggleStates in the function.
Edited and fixed now. :oops:
Lem2001
Posts: 127
Joined: 27 Jun 2017, 17:59

Re: Hotkey to toggle window size

22 Oct 2020, 21:54

Success! :dance:

I temporarily changed the timer to 1 minute so that I could more quickly see the changes when viewing the array count. The count was retained after all windows were closed, and then a minute later the array count was back at zero.

My reason for asking about the separation of the function part of the code from the array / timer part is that things will still get out of sync if the script is restarted while the line-number bar is open (making windows size in the wrong direction). I restart my hotkeys script a lot, because I'm always adding to it, adjusting existing items, or fixing my screw-ups (and you obviously need to restart for any change to take effect). Usually I don't like to have line-numbers visible (for general plain text stuff) but for scripts its obviously different. So there's a high likelihood that I'd be restarting the script with the number bar open (causing a sync issue).

However, given the additional complexity of getting scripts to communicate with each other (and my track record of not being able to even paste things correctly - lol) it would be easier to just run the script again whenever it gets out of sync. But then I had the idea of moving that one particular hotkey wholesale to my other permanently running script. It just contains 3 timers and is rarely ever touched or even looked at (because there's no need; it just works).

I tried it out and moved the whole of the F9:: subroutine, the array reset, and the new timer, and put them all in my other script. It works great, and now I can continue to freely edit in my main hotkeys script and restart it as much as I want without ever having any windows get out of sync. I put a note in my main hotkeys script (where the other remaining Codepad hotkeys still reside) so that I don't drive myself crazy in future wondering how this window size feature is working when there is no code for it anywhere in my main script (which is exactly the kind of thing that I can envisage happening).

Thanks for all your help and your incredible patience.
User avatar
Jim Dunn
Posts: 478
Joined: 01 Sep 2020, 20:21
Location: NSW

Re: Hotkey to toggle window size

22 Oct 2020, 22:10

You're very welcome. I'm glad if I helped. :)
Lem2001 wrote:
22 Oct 2020, 21:54
... moved the whole of the F9:: subroutine, the array reset, and the new timer, and put them all in my other script.
...and the initial array declaration, of course... ;)

There's no real reason not to just leave that timer check at one minute - 10 minutes was just a number.
It's not going to impact performance running once a minute, because it doesn't do anything intensive, and a one minute period might actually be better.
I have several much more complicated timer routines running as often as every second, and ahk never shows as more than 0% CPU and about 2 - 2.5 K in task manager.

Your solution of moving it all to your "timers" script is a good one - I'm all for not introducing extra complexity (and more things to "fail") unnecessarily. :thumbup:

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: dunnerca and 145 guests