A caution about combining Gui.Add and onEvent functions

Put simple Tips and Tricks that are not entire Tutorials in this forum
User avatar
DrReflex
Posts: 42
Joined: 25 May 2015, 02:57
Location: Greer, SC

A caution about combining Gui.Add and onEvent functions

19 Apr 2023, 11:39

Combining Gui.Add() and onEvent() functions on a single line is appealing as a programming technique (see below). However, this can lead to unexpected issues.

Gui.Add() returns the GuiControl object (GuiCtrl) for the control that was added. This is a powerful reference that you will want to have if you are going to interact with the control.

OnEvent() when registered to a GuiCtrl without a Callback returns a null string

When you combine the Gui.Add() and the onEvent() functions in a single line of code (see below) the final value returned is the NullString returned by onEvent() not the GuiCtrl returned by GuiAdd(). When combined the GuiCtrl object returned by Gui.Add() is used to register the onEvent() with the newly added control and the return value is a string returned by onEvent().

THIS LINE IS VALID CODE:
RA := main.addRadio("vTesting", "Test1").onEvent("Click", React) ;here RA = NullString (no onEvent callback)
...
ControlClick RA ;Generates an error because RA is not a valid GuiCtrl object

IT IS NOT THE SAME AS THE FOLLOWING (WHICH IS ALSO VALID CODE):
RA := main.addRadio("vTesting", "Test1") ;here RA = the GuiCtrl object for the added radio button
RA.onEvent("Click", React)
...
ControlClick RA ;Clicks the RA radio button

--- THIS PRECAUTION HOLDS FOR COMBINING FUNCTIONS ANYWHERE IN AHK ---

Moderator's Note: Moved from Scripts and Functions to Tips and Tricks
User avatar
FanaticGuru
Posts: 1906
Joined: 30 Sep 2013, 22:25

Re: A caution about combining Gui.Add and onEvent functions

20 Apr 2023, 19:41

DrReflex wrote:
19 Apr 2023, 11:39
Combining Gui.Add() and onEvent() functions on a single line is appealing as a programming technique (see below). However, this can lead to unexpected issues.

.OnEvent on the end looked cool when I first saw it but I pretty much never do it now unless the script is really simple. You can do this if you like to keep on one line:
MyButton := MyGui.Add('Button',,'Ok'), MyButton.OnEvent('Click', Handler_Click)

Here is how I like doing things.

Code: Select all

guiInfoDialog := Gui('+AlwaysOnTop -MinimizeBox')
guiInfoDialog.TextW := guiInfoDialog.Add('Text', 'y20', 'Width')
guiInfoDialog.EditW := guiInfoDialog.Add('Edit', 'r1 w40 x50 yp')
guiInfoDialog.TextH := guiInfoDialog.Add('Text', 'xp+60 yp', 'Height')
guiInfoDialog.EditH := guiInfoDialog.Add('Edit', 'r1 w40 xp+50 yp')
guiInfoDialog.CheckRatio := guiInfoDialog.Add('CheckBox', 'xp+60 yp-10', 'Lock Ratio')
guiInfoDialog.CheckRatio.OnEvent('Click', guiInfoDialog_Click)
guiInfoDialog.CheckWH := guiInfoDialog.Add('CheckBox', 'xp yp+20', 'Display W x H')
guiInfoDialog.CheckWH.OnEvent('Click', guiInfoDialog_Click)
guiInfoDialog.ButtonApply := guiInfoDialog.Add('Button', 'Default xm yp+40 w80', 'Apply')
guiInfoDialog.ButtonApply.OnEvent('Click', guiInfoDialog_Click)
guiInfoDialog.ButtonOK := guiInfoDialog.Add('Button', 'yp xp+100 w80', 'Ok')
guiInfoDialog.ButtonOK.OnEvent('Click', guiInfoDialog_Click)
guiInfoDialog.ButtonCancel := guiInfoDialog.Add('Button', 'yp xp+100 w80', 'Cancel')
guiInfoDialog.ButtonCancel.OnEvent('Click', guiInfoDialog_Click)
guiInfoDialog.Show

F12:: guiInfoDialog.Show ; Show dialog after closing with Ok or Cancel

guiInfoDialog_Click(GuiCtrlObj, Info)
{
	Switch GuiCtrlObj.Text
	{
		Case 'Lock Ratio', 'Display W x H' :
			{
				ToolTip GuiCtrlObj.Text ' = ' GuiCtrlObj.Value
			}
		Case 'Apply':
			{
				Width := guiInfoDialog.EditW.Value, Height := guiInfoDialog.EditH.Value
				ToolTip 'W x H ' Width 'x' Height
			}
		Case 'Ok':
			{
				Width := guiInfoDialog.EditW.Value, Height := guiInfoDialog.EditH.Value
				ToolTip 'W x H ' Width 'x' Height '`nOk'
				guiInfoDialog.Hide
			}
		Case 'Cancel':
			{
				ToolTip 'Cancel'
				guiInfoDialog.Hide
			}
	}
}

I like to store all my controls in the same object as the Gui object. It is then easy to enumerate through all the controls for something like a Size event for example.

I also like to limit my OnEvent functions and have functions handle multiple events and then just sort out which control within the handler function.

FG
Hotkey Help - Help Dialog for Currently Running AHK Scripts
AHK Startup - Consolidate Multiply AHK Scripts with one Tray Icon
Hotstring Manager - Create and Manage Hotstrings
[Class] WinHook - Create Window Shell Hooks and Window Event Hooks
User avatar
kczx3
Posts: 1643
Joined: 06 Oct 2015, 21:39

Re: A caution about combining Gui.Add and onEvent functions

20 Apr 2023, 20:47

Can’t you just loop over the GUI object to loop through the controls?
User avatar
FanaticGuru
Posts: 1906
Joined: 30 Sep 2013, 22:25

Re: A caution about combining Gui.Add and onEvent functions

21 Apr 2023, 12:32

kczx3 wrote:
20 Apr 2023, 20:47
Can’t you just loop over the GUI object to loop through the controls?

That is true. That was a poor example.

The main take away is that a Gui object is still very much like a standard object in that other objects can be nested inside it. Whether they are other gui, gui controls, maps, arrays, properties, etc. Even functions, classes, and methods, although I have not tried or had a need for that yet.

I often keep position information in the Gui object for its controls and then only enumerate through certain controls when resizing with rules for how different groups are moved.

I was trying to keep it simpler, but he is a more robust example of storing additional information in the Gui object.

Code: Select all

guiInfoDialog := Gui('+AlwaysOnTop -MinimizeBox')
guiInfoDialog.Text := {}, guiInfoDialog.Edit := {}, guiInfoDialog.Check := {}, guiInfoDialog.Button := {}
guiInfoDialog.Text.W := guiInfoDialog.Add('Text', 'y20', 'Width')
guiInfoDialog.Edit.W := guiInfoDialog.Add('Edit', 'r1 w40 x50 yp')
guiInfoDialog.Text.H := guiInfoDialog.Add('Text', 'xp+60 yp', 'Height')
guiInfoDialog.Edit.H := guiInfoDialog.Add('Edit', 'r1 w40 xp+50 yp')
guiInfoDialog.Check.Ratio := guiInfoDialog.Add('CheckBox', 'xp+60 yp-10', 'Lock Ratio')
guiInfoDialog.Check.Ratio.OnEvent('Click', guiInfoDialog_Click)
guiInfoDialog.Check.WH := guiInfoDialog.Add('CheckBox', 'xp yp+20', 'Display W x H')
guiInfoDialog.Check.WH.OnEvent('Click', guiInfoDialog_Click)
guiInfoDialog.Button.Apply := guiInfoDialog.Add('Button', 'Default xm yp+40 w80', 'Apply')
guiInfoDialog.Button.Apply.OnEvent('Click', guiInfoDialog_Click)
guiInfoDialog.Button.OK := guiInfoDialog.Add('Button', 'yp xp+100 w80', 'Ok')
guiInfoDialog.Button.OK.OnEvent('Click', guiInfoDialog_Click)
guiInfoDialog.Button.Cancel := guiInfoDialog.Add('Button', 'yp xp+100 w80', 'Cancel')
guiInfoDialog.Button.Cancel.OnEvent('Click', guiInfoDialog_Click)
guiInfoDialog.Button.Apply.Help := 'Set Input Without Closing Gui'
guiInfoDialog.Button.Ok.Help := 'Set Input and Close Gui'
guiInfoDialog.Button.Cancel.Help := 'Close Gui Without Setting Input'
guiInfoDialog.Show

F11:: ; Clear all Check Marks (something similar could be used for saving and loading settings to an ini file)
{
	For each, GuiCtrlObj in guiInfoDialog.Check.OwnProps()
		GuiCtrlObj.Value := 0
}

F12:: guiInfoDialog.Show ; Show dialog after closing with Ok or Cancel

guiInfoDialog_Click(GuiCtrlObj, Info)
{
	Switch GuiCtrlObj.Text
	{
		Case 'Lock Ratio', 'Display W x H':
			{
				ToolTip GuiCtrlObj.Text ' = ' GuiCtrlObj.Value
			}
		Case 'Apply':
			{
				Width := guiInfoDialog.Edit.W.Value, Height := guiInfoDialog.Edit.H.Value
				ToolTip 'W x H ' Width 'x' Height
			}
		Case 'Ok':
			{
				Width := guiInfoDialog.Edit.W.Value, Height := guiInfoDialog.Edit.H.Value
				ToolTip 'W x H ' Width 'x' Height '`nOk'
				guiInfoDialog.Hide
			}
		Case 'Cancel':
			{
				ToolTip 'Cancel'
				guiInfoDialog.Hide
			}
	}
}

; Setup message events and handling for hovering
OnMessage(WM_MOUSEMOVE := 0x0200, OnMouseEvent)
OnMessage(WM_MOUSELEAVE := 0x02A3, OnMouseEvent)
OnMouseEvent(wp, lp, msg, hwnd)
{
	Static TME_LEAVE := 0x2, onHover := false

	If msg = WM_MOUSEMOVE && !onHover
	{
		TRACKMOUSEEVENT := Buffer(8 + A_PtrSize * 2)
		NumPut('UInt', TRACKMOUSEEVENT.Size,
			'UInt', TME_LEAVE,
			'Ptr', hwnd,
			'Ptr', 10, TRACKMOUSEEVENT)
		DllCall('TrackMouseEvent', 'Ptr', TRACKMOUSEEVENT)
		HelpToolTip(onHover := true, hwnd)
	}
	If msg = WM_MOUSELEAVE
	{
		HelpToolTip(onHover := false, hwnd)
	}
}

HelpToolTip(Hover, Hwnd)
{
	If Hover
	{
		Try
		{
			CoordMode('ToolTip', 'Window')
			GuiCtrlObj := GuiCtrlFromHwnd(Hwnd)
			GuiCtrlObj.GetPos(&X, &Y, &Width, &Height)
			ToolTip(GuiCtrlObj.Help, X, Y + Height)
		}
	}
	Else
		ToolTip
}
The Gui object can end up like a mini-database with everything about it in one object. It can be dynamic too, like saving a history of previous 10 inputs for every edit control with an auto-complete feature. There are lots of possibilities.

But I understand it could be a little verbose for some peoples taste.

FG
Hotkey Help - Help Dialog for Currently Running AHK Scripts
AHK Startup - Consolidate Multiply AHK Scripts with one Tray Icon
Hotstring Manager - Create and Manage Hotstrings
[Class] WinHook - Create Window Shell Hooks and Window Event Hooks
just me
Posts: 9466
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: A caution about combining Gui.Add and onEvent functions

22 Apr 2023, 04:55

Single line and single expression:

Code: Select all

(RA := main.addRadio("vTesting", "Test1")).onEvent("Click", React) ;here RA = the GuiCtrl object for the added radio button)
User avatar
FanaticGuru
Posts: 1906
Joined: 30 Sep 2013, 22:25

Re: A caution about combining Gui.Add and onEvent functions

24 Apr 2023, 14:50

just me wrote:
22 Apr 2023, 04:55
Single line and single expression:

Code: Select all

(RA := main.addRadio("vTesting", "Test1")).onEvent("Click", React) ;here RA = the GuiCtrl object for the added radio button)

That seems pretty good. I am surprised I have not seen it or thought of it myself.

Makes for kind of long lines but I like having a control and its event on the same line.

FG
Hotkey Help - Help Dialog for Currently Running AHK Scripts
AHK Startup - Consolidate Multiply AHK Scripts with one Tray Icon
Hotstring Manager - Create and Manage Hotstrings
[Class] WinHook - Create Window Shell Hooks and Window Event Hooks

Return to “Tips and Tricks”

Who is online

Users browsing this forum: No registered users and 15 guests