Added as a Special Feature to AutoHotkey V1.1, You Can Quickly Bind Unique Data to GUI Controls for Passing to Functions—It’s Even Easier in V2.0. Add This One to Your Bag of AutoHotkey Tricks!
Sometimes in my explorations, I come across an unexpected gem. I dig into many aspects AutoHotkey merely because they exist—having no idea how a technique might affect my scriptwriting. Whenever I uncover a feature that switches on a light, I must admit I get a little excited. Interestingly, if I had not been rummaging through AutoHotkey V2.0, I may not have ever understood the significances of this latest revelation for GUI pop-up windows in V1.1 scripts.
* * *
In a GUI (Graphical User Interface) pop-up window, passing the right data to a gLabel subroutine (or function) from a GUI control can get complicated. A couple of the more common methods includes calling the Gui, Submit command to store control values or using a technique for capturing control information, such as the MouseGetPos command or the special gLabel alternative function:
CtrlEvent(CtrlHwnd, GuiEvent, EventInfo, ErrLevel:="")
While I can usually find a way to solve the data passing problem, I often find the answer awkward.
As seen in my last blog (where I needed to pass an image filename in the PictureSounds.ahk script to the alternative gLabel CtrlEvent() function), I went through a number of gymnastics to get a final result. I first capture the GUI control’s window handle (Hwnd). Next, I used the GuiControlGet command to capture the name of the GUI control (the image filename). Finally, I used IniRead to lookup the corresponding sound filename in the primary data file. Even in V2.0 which doesn’t offer the gLabel CtrlEvent() function, I settled on the MouseGetPos function to identify the control before capturing its name with the ControlGetText() function.
Stumbling Across AutoHotkey Techniques
As I investigated the GUI objects in V2.0, I noticed in passing that the online documentation uses the Func(“FunctionName”).Bind(Data) method in a number of GUI examples. [What?] At the time, the code looked like another way to confuse my efforts without adding much functionality. [Boy, was I wrong!] I determined that I would go back later to understand how this method works.
To my surprise, the Func.Bind() method makes the passing of special data to functions in the PictureSounds.ahk script incredibly simple in both V1.1 and V2.0—eliminating a ton of clumsy code. By connecting the GUI control directly to the function, I could use the “Functor” to eliminate a number of my GUI control identification steps in the main PictureSounds.ahk function. In fact, I can now drop the INI file format since ultimately I no longer need the IniRead command—although I continue to require a text database. If you do any work with GUI pop-up windows, you’ll find this tip well worth learning.
Learning the Func.Bind(Data) Method
While rummaging through the V2.0 documentation, I often encounter various enigmatic structures and syntax—mostly in relation to objects. This forced me to venture a little at a time into learning how the new structures work. That’s when I discovered in a number of V2.0 Gui Object examples using the OnEvent() function to call the Func(“FunctionName”).Bind(Data) method. [Humm.] In the past, I’ve always found ways around unfamiliar code but, at a minimum, I wanted to understand why this particular code existed at all. I started with a simple Func.Bind() demonstration script in the V2.0 documentation.
I wasn’t particularly impressed with that demo script (which passes a constant to a function) until I realized that I wanted to do exactly that in the PictureSounds.ahk script—feeding the image filename directly to the main function as a Key for locating the audio filename in the INI file. (While playing with AutoHotkey V2.0, I always look for reasons which, when the time comes, might urge me to make the switch. I think I may have stumbled across one.)
Since AutoHotkey V1.1 uses the GUI command rather than the V2.0 GUI object, I checked to see if AutoHotkey limited the Functor to V2.0. Although not obvious, it turns out that V1.1 does support a form of the GUI control/function binding feature:
[v1.1.20+]: If not a valid label name, a function name can be used instead. Alternatively, the GuiControl command can be used to associate a function object with the control.
While a little more complex, you can get the same data binding benefits for GUI controls in V1.1 as in V2.0. With a little digging around, I pulled the pieces together.
(To be honest, I still don’t fully comprehend the implications of BoundFunc Objects in situations outside of GUI controls—in particular the ObjBindMethod() function. It seems I need to do a little more digging. It’s probably another hidden gem, but that’s for another time. It’s important to note that you don’t need to wait for V2.0 to take advantage of these benefits. You can find them in V1.1.)
Updating the V1.1 PictureSounds.ahk Script with Func.Bind()
The original PictureSounds.ahk script uses the special CtrlEvent() function to identify the clicked Picture control. That’s the part we want to eliminate (shown in red below):
Loop, Read, PictureSounds.ini
{
If A_Index = 1
Continue
picture_array := StrSplit(A_LoopReadLine, "=")
Gui, Add, Picture, x50 w50 h-1 gCtrlEvent, % picture_array[1]
}
Gui, show, w150
CtrlEvent(CtrlHwnd, GuiEvent, EventInfo, ErrLevel:="")
{
GuiControlGet, OutputVar , , %CtrlHwnd%,
; MouseGetPos, , , , OutputVar ; Alternative command
IniRead, Sound, PictureSounds.ini, Sounds, %OutputVar%
SoundPlay, %Sound%
}
This script gave me a starting point for figuring out how to bind an image in the GUI pop-up to a sound file without all the intermediate variables and steps.
Binding Data to a GUI Control in AutoHotkey V1.1
AutoHotkey V1.1 requires two steps for binding a GUI control parameter (or any other value) to a function.
- Create the Function Object (Functor) which relates the function to the constant. In this case, the image filename (picture_array[1]) binds to the function AudioPlay() (the first red line below).
- Next, use the GuiControl command to attach the function as a gLabel to the control (the second red line below):
Loop, Read, PictureSounds.ini { If A_Index = 1 Continue picture_array := StrSplit(A_LoopReadLine, "=") Gui, Add, Picture, x50 w50 h-1, % picture_array[1] fn := Func("AudioPlay").Bind(picture_array[1]) GuiControl +g, % picture_array[1], % fn } Gui, Show, w150 AudioPlay(OutputVar) { IniRead, Sound, PictureSounds.ini, Sounds, %OutputVar% SoundPlay, %Sound% }
The GuiControl +g option adds a gLabel subroutine to the named control—in this case, calling the AudioPlay() function. In the GuiControl line of code, the script uses the % forced expression operator to evaluate both the name of the graphics file (picture_array[1]) and the Function Object (fn from the first red line). Direct replacement of the first expressions (%picture_array[1]%) won’t work since it includes the illegal characters found in the V1.1 array variable ([ ]). The %fn% variable replacement does work as an alternative to the % fn forced expression.
I eliminated all the other fustering (click the link and see verb definition 4 under Etymology 2) with Hwnds and retrieving control names by sending the INI search Key (the image filename) directly to the function. Much cleaner.
Use Any Data You Like
Lightbulb! Then, I asked myself, “Rather than pass the image filename as a lookup Key, why not just pass the name of the audio filename directly to the function?” After all, since the control data comes from the same INI file, the second element of the parsed array (picture_array[2]) already contains the audio filename.
Loop, Read, PictureSounds.ini
{
If A_Index = 1
Continue
picture_array := StrSplit(A_LoopReadLine, "=")
Gui, Add, Picture, x50 w50 h-1, % picture_array[1]
fn := Func("AudioPlay").Bind(picture_array[2])
GuiControl +g, % picture_array[1], % fn
}
Gui, Show, w150
AudioPlay(Sound)
{
SoundPlay, %Sound%
}
This drops the need for the INI search in the function— eliminating the INI file lookups. The AudioPlay() function can’t get much simpler than that.
After loading the new PictureSoundsBind.ahk script, AutoHotkey binds the name of each audio file directly to the appropriate Picture control. In fact, since the script only uses the data file once when loading the GUI pop-up, you can drop the INI format (and remove the Section data line [Sounds]) and use any text file, thus eliminating two more lines of code from the initial Loop:
If A_Index = 1 Continue
That’s a lot of app for very little code!
What the Script Loop Writes
To get a better understanding of how binding the audio filename to the Gui, Add, Picture control works, check out this sample of the PictureSoundsBind.ahk code when you eliminate the Loop and hardcode the parsed filenames directly into the script. Taken from the INI lookup file in this previous blog:
[Sounds] Dancing Dogs.jpg=dog_bark_x.wav Cheese Burger.jpg=cow-madcow.wav Phone.jpg=Windows Ringin.wav
The corresponding hardcoded script:
Gui, Add, Picture, x50 w50 h-1, Dancing Dogs.jpg fn := Func("AudioPlay").Bind("dog_bark_x.wav") GuiControl +g, Dancing Dogs.jpg, % fn Gui, Add, Picture, x50 w50 h-1, Cheese Burger.jpg fn := Func("AudioPlay").Bind("cow-madcow.wav") GuiControl +g, Cheese Burger.jpg, % fn Gui, Add, Picture, x50 w50 h-1, Phone.jpg fn := Func("AudioPlay").Bind("Windows Ringin.wav") GuiControl +g, Phone.jpg, % fn Gui, Show, w150 AudioPlay(Sound) { SoundPlay, %Sound% }
The script continues to use the forced expression (% fn) in the GuiControl lines of code since fn acts as a Function Object (Functor).
Do It in AutoHotkey V2.0
The AutoHotkey V2.0 PictureSoundsBind.ahk2 script looks very similar to the V1.1 script except it uses the V2.0 GUI object with the OnEvent() function rather than the V1.1 GUI command:
PictureSound := GuiCreate() Loop Read, "PictureSounds.ini" { If A_Index = 1 Continue picture_array := StrSplit(A_LoopReadLine, "=") PictureSound.Add("Picture","w50 h-1",picture_array[1]) .OnEvent("Click",Func("AudioPlay").bind(picture_array[2])) } PictureSound.Show("AutoSize Center") AudioPlay(Sound) { SoundPlay Sound }
(The PictureSound.Add() control object word wraps the OnEvent() function to the next line using AutoHotkey line continuation techniques for display purposes only.)
You can create Functors to bind any number of GUI control parameters to the function as long as the number of parameters matches in the function.
From now on, whether working in AutoHotkey V1.1 or V2.0, I plan to watch my GUIs closely for opportunities to make them more efficient (and save code) by binding data between the GUI control and a function.
This post was proofread by Grammarly
(Any other mistakes are all mine.)
(Full disclosure: If you sign up for a free Grammarly account, I get 20¢. I use the spelling/grammar checking service all the time, but, then again, I write a lot more than most people. I recommend Grammarly because it works and it’s free.)
[…] I previously used the Func.Bind() method with a GUI (Graphical User Interface) control in a 2018 blog, it took me a little while to figure out how to make this “Functor” technique work in […]
LikeLike
[…] and Education” of Jack’s Motley Assortment of AutoHotkey Tips and discussed in “Use BoundFunc Object [Func.Bind()] to Pass GUI Control Data (An AutoHotkey GUI Revelation)” and “Increase the Flexibility of Menus by Passing Data with the […]
LikeLike