Use BoundFunc Object [Func.Bind()] to Pass GUI Control Data (An AutoHotkey GUI Revelation)

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.

*          *          *

GraphicSoundsIn 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

Library BenefitsAs 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 the 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 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 forces 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 use 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.

  1. 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).
  2. 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 Small 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?” Afterall, 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.

jack

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.)

 

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s