AutoHotkey Tip of the Week: Increase the Flexibility of Menus by Passing Data with the BoundFunc Object

Streamline and Add Power to Hotstring Menus by Binding Action Parameters Directly to Each Menu Item

*          *          *

If you use AutoHotkey menus, then you may find this blog the most useful menu tip yet. At first, using the BoundFunc Object to pass data may seem confusing, but, once understood, it opens up many more opportunities for taking advantage of menus in your AutoHotkey scripts.

*          *          *

HotstringSubMenus

As often happens when working on an AutoHotkey script, a deeper understanding of the available tools completely changes the direction of the project. While all of the Menu tricks I’ve offered in the past HotstringMenu.ahk scripts still work (and you may want to continue using many of those techniques), the following approach which combines arrays, the variadic function parameter, and the boundfunc object creates a cleaner, more functional structure for generating Hotstring replacement menus. In short, implementing the boundfunc object allows me to drop many of those previous menu tricks and build menus using virtually any menu item format without regard for their later use through the value of the A_ThisMenuItem variable.

Library Benefits

Over the last number of weeks, my explorations of various AutoHotkey Menu tricks expanded to using arrays in the variadic function parameter. Last time, I briefly mentioned using the BoundFunc Object in menus as a possible alternative technique to some of the menu manipulations I devised for passing menu data. This time, I dig deeper into the AutoHotkey feature and demonstrate how to use the BoundFunc Object with Menu items to write a simpler Hotstring menu creating function. I call the function HotstringMenuF() and include it in the posted HotstringSubMenu.ahk script.

Passing variable data by binding values to function parameters breaks one of the primary constraints of AutoHotkey Menus—limiting available data to the three built-in menu variables: A_ThisMenuItem, A_ThisMenuItemPos, and A_ThisMenu. Now, by defining a bound function data parameter and calling it via a menu item, we can pass virtually any data with a click of the mouse.

While this blog addresses how to bind data to a function called by a menu item, the same HotstringSubMenu.ahk script includes a number of other AutoHotkey features, plus, a couple of new tricks as follows:

  1. Implements a new function, TopMenu(), for creating a top-level menu containing the Hotstring submenus. (The script also sets up individual Hotstrings for bypassing the top-level menu and directly activating each submenu.)
  2. Uses the variadic function parameter for both the function definitions and function calls directly employing arrays by name.
  3. For persistence and ready access, the script sets up each menu at launch rather than recreating and destroying each on demand.
  4. The script automatically adds single-key shortcuts to each item in every menu.
  5. Long menus (over nine items) use letters rather than numbers as single-key shortcuts.
  6. Includes the option to use simple arrays for standard text or associative arrays for special characters or emojis (no spaces allowed).
  7. Employs two different tricks for identifying simple versus associative arrays.
  8. Takes advantage of a trick for obtaining the length of both simple and associative arrays.

I’ll save the discussion of these other script components for future blogs. For now, I concentrate on how to use the BoundFunc Object in menus.

Using BoundFunc Objects in Menus

Binding data to individual menu items eliminates the need to manipulate the MenuItemName before sending the replacement text. You can completely bypass the limitations of the basic menu setup. For example, when inserting replacement text, the script no longer needs to remove any extras from A_ThisMenuItem, such as single-key menu shortcuts, before inserting the new text.

Although 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 menus. After pulling the various pieces together, the online documentation (and critical posts in the AutoHotkey forum) ultimately explained how to get it done—although many new AutoHotkey users may not find the explanations in the Internet sources easy to understand. With the one exception noted below in the “Calling a Function Directly by Name” section, you cannot simply add a function name to the Menu command. The key to success resides in the words “a single variable reference containing a function object.” To make this trick this operational, you must save the defined bound function to a variable name:

Handler := Func("InsertFunction").Bind(Each,Item)

Then, append the variable containing the bound function to the Menu command:

Menu, % MenuName, Add, % Item, % Handler

The called function in this example, InsertFunction(), must exist and include the two bound parameters at a minimum:

InsertFunction(Key, Item, ItemName, ItemPos, MenuName)
{
  If (Key ~= "^\d*$")  ; Check for simple array
    SendInput {raw}%Item%%A_EndChar%
  Else
    SendInput {raw}%Key%%A_EndChar%
}

By including these three snippets in the menu setup, the script, first, passes the two bound parameter values as independent variables to the menu item itself, then, second, from the menu item (when clicked) to the function.

Note: Any function called by a menu item must include the bound parameters at the front of the parameters list, but it can receive up to three additional built-in parameters (representing A_MenuItemName, A_MenuItemPos, and A_MenuName—in that order). These three standard parameters always appear last—although you do not need to include them at all in order to pass the bound function parameters. Since the example function above does not use the built-in data parameters, it could appear as follows:

InsertFunction(Key, Item)
{
  If (Key ~= "^\d*$") ; Check for simple array
    SendInput {raw}%Item%%A_EndChar%
  Else
    SendInput {raw}%Key%%A_EndChar%
}

Passing data with a BoundFunc Object in menus does not depend upon the standard variables found in AutoHotkey menus. This allows scripts to break the menu data-passing barriers making them more powerful and flexible.

Calling a Function Directly by Name

As I mentioned above, in one instance you can directly add a function name rather than a variable containing the function name to the menu command:

Menu, % MenuName, Add, % Item, InsertFunction

However, you have a few considerations when directly calling a function:

  1. The function can contain between zero and three parameters but no more than three, i.e, no bound parameters.
  2. The parameters are predefined as ItemName, ItemPos, MenuName—in that order:
    FunctionName(ItemName, ItemPos, MenuName)
  3. Rather than passing the values with parameters, as shown above, the script can directly access the build-in variables (A_MenuItemName, A_MenuItemPos, and A_MenuName) within the function in the same manner as in Label subroutines. If the script only uses these standard variable values, their availability eliminates the need to use a function in place of a subroutine. (Plus, you’ll also find these standard built-in menu variables valid for functions passing bound parameters.)

You may prefer to use the standard menu function format over a Label subroutine—although you gain little unless you pass additional parameters bound to the function.

The New BoundFunc Object Script

To download the new script, visit HotstringSubMenu.ahk. After loading the script below, open the sample menus using the Hotstring “tp” followed by the backtick key (`) for top-level menu; or “a” for the arrows submenu, “b” for the bovines submenu, “f” for the fractions submenu, and “g” for greetings submenu—each last four followed by the backtick key (`):

; This AutoExecute section defines sample arrays 
; and launches the menu building functions

HotstringMenuAutoExecute:

Greetings := ["Hi!","Hello!","What's up?","How's it going?"]
FractionsA := {⅒: "one-tenth",⅑: "one-ninth"
              ,⅛: "one-eight",⅐: "one-seventh"
              ,⅙: "one-sixth Brk",⅕: "one-fifth"
              ,¼: "one-fourth",⅓: "one-third Brk"
              ,⅜: "three-eights",⅖: "two-fifths"
              ,½: "one-half",⅗: "three-fifths"
              ,⅝: "five-eights",⅔: "two-thirds"
              ,¾: "three-fourths",⅘: "four-fifths"
              ,⅚: "five-sixths",⅞: "seven-eights"}
ArrowsA := {⇐: "Left arrow",⇔: "Double arrow"
           ,⇒: "Right arrow",⇑: "Up arrow"
           ,⇓: "Down arrow"}
Bovines := {🐂: "Ox",🐃: "Water Buffalo",🐄: "Cow"
           ,🐄🐂🐃: "All three!"}

TopMenu := {FractionsA: "Fractions`tf``"
           , ArrowsA: "Arrows`ta``"
           , Bovines: "Bovines 🐂🐃🐄`tb``"
           , Greetings: "Greetings`tg``"}

TopMenu("MainMenu",TopMenu*)
Return

:x*:tp``::Menu, MainMenu, Show
:x*:a``::Menu, ArrowsA, Show
:x*:f``::Menu, FractionsA, Show
:x*:b``::Menu, Bovines, Show
:x*:g``::Menu, Greetings, Show

; Function for creating top menu and all submenus HotstringMenuF()
TopMenu(MenuName,MenuItems*)
{
  For Each, Item in MenuItems {
    HotstringMenuF(Each,%Each%*)
    Menu, %MenuName%, Add, % "&" A_Index " " Item, % ":" Each
  }
}

HotstringMenuF(MenuName,MenuArray*)
{
  ArrayLength := MenuArray.SetCapacity(0) ; Get array size
  For Each, Item in MenuArray {
    If (ArrayLength < 10)
    Shortcut := "&" . A_Index
  Else 
    Shortcut := "&" . Chr(A_Index+96)
  If (InStr(Item,"Brk")) ; Add column breaks to long menus
  {
    Item := StrReplace(Item,"Brk")
    Options := "+BarBreak"
  }
  Else 
    Options := ""

; Bind output data to the InsertFunction()
  Handler := Func("InsertFunction").Bind(Each,Item)

  If (Each = A_index) ; Simple array
    Menu, % MenuName, Add, % Shortcut " — " Item, % Handler
        , % Options
  Else
    Menu, % MenuName, Add, % Shortcut " " Each " — " Item
                , % Handler, % Options
  }
}

InsertFunction(Key,Item)
{
  If (Key ~= "^\d*$")
    SendInput {raw}%Item%%A_EndChar%
  Else
    SendInput {raw}%Key%%A_EndChar%
}

 

Click the Follow button at the top of the sidebar on the right of this page for e-mail notification of new blogs. (If you’re reading this on a tablet or your phone, then you must scroll all the way to the end of the blog—pass any comments—to find the Follow button.)

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

 

 

 

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 )

Connecting to %s