Writing AutoHotkey Functions to Make Life Easier

If You Find That You Often Rewrite the Same or Similar Code, Then Investigate Using a Function

Updated: March 7, 2020

A Question about AutoHotkey Functions:

Hi Jack, I very much appreciate your simple and easy to understand explanations and would really appreciate it if you would do a blog on how to use Functions in AutoHotkey. I’ve put together a tool for work that writes a series of different text strings based on menu choices and need to be able to log their use. My efforts to do so by using Functions have come to naught and I have fallen back to a series of several repetitive statements that I know could be done much more neatly. I’d be happy to send you an example if you’d like to work from that. Thanks! [Example received.]

Gary Cassidy

Hi, Gary,  you’re right. You can make your scripting life simpler (and easier) by replacing repetitious code with AutoHotkey user-defined functions. That is often the purpose of functions, although you’ll discover a variety of ways to use them. The beauty of a function lies in the fact that once written you can use it virtually anywhere in the script over and over again without rewriting the code.

*          *          *

AHKNewCover200Note for new readers: New to AutoHotkey? Check out this Introduction to AutoHotkey: A Review and Guide for Beginners or my book Jack’s Beginner’s Guide to AutoHotkey.

*          *          *

User-Defined Function Format

A user-defined function breaks down into two parts:

  • First, the name of the function used when running (or calling) it.
  • Second, the parameters section located within the set of parentheses.
FunctionName(Parameter1, Parameter2, Parameter3,…)

AutoHotkey assigns (or defines) the function name when you write the function. A function must include the set of parentheses enclosing the variable names of data passed to the function for processing. You can add any number of parameters (or none at all) but the number of data items (separated by commas) in the calling function must match the number of parameters defined by the function itself. When executing the function, the data items must appear in the same order in the calling function as in the function itself.

The example code that you sent me that you would like to turn into a function follows:

; ***********************************************
 ; * *
 ; * *
 ; * LOGGING FUNCTIONS *
 ; * *
 ; * *
 ; ***********************************************

; TOTAL USEAGE COUNT (GLOBAL COUNT OF ALL NOTES)
 IniRead, UseageCount, UseageLog.ini, TotalUses, Count
 UseageCount++
 iniwrite, %UseageCount%, UseageLog.ini, TotalUses, Count

; TOTAL USEAGE COUNT BY NOTE TYPE
 IniRead, UseageCount, UseageLog.ini, TotalUses, PA Review
 UseageCount++
 iniwrite, %UseageCount%, UseageLog.ini, TotalUses, PA Review

; TOTAL USEAGE COUNT BY USER
 IniRead, UseageCount, UseageLog.ini, %A_UserName%, Total Uses
 UseageCount++
 iniwrite, %UseageCount%, UseageLog.ini, %A_UserName%, Total Uses

; TOTAL USEAGE COUNT BY NOTE TYPE FOR EACH USER
 IniRead, UseageCount, UseageLog.ini, %A_UserName%, PA Review
 UseageCount++
 iniwrite, %UseageCount%, UseageLog.ini, %A_UserName%, PA Review

; DATE/TIME/USER/NOTE TYPE
 UseDate = %A_DD%/%A_MM%/%A_YYYY%
 UseTime = %A_Hour%:%A_Min%
 FileAppend
   ,%UseDate% %A_Space% %UseTime% %A_Space% %A_UserName% %A_Space% (PA Review)`n
   , UserTimeDate.log

There are four repetitive snippets in this example. If you find yourself writing the same code over-and-over again (probably a cut-and-paste with modifications), then you should consider writing a function. The last snippet in your example, “DATE/TIME/USER/NOTE TYPE”, occurs only once unique and probably not worth turning into a function—unless you find other places in your script where you need to do perform the same operation.

Creating an AutoHotkey Function to Replace Repetitious Code

As a first step toward writing an AutoHotkey user-defined function, separate the repetitive code and the unique code. The repeated code constitutes the basis for the script within the function and the unique data items act as the parameters passed to the function within the function’s parentheses:

IniRead, UseageCount, UseageLog.ini, TotalUses, Count
 UseageCount++
 iniwrite, %UseageCount%, UseageLog.ini, TotalUses, Count

IniRead, UseageCount, UseageLog.ini, TotalUses, PA Review
UseageCount++
Iniwrite, %UseageCount%, UseageLog.ini, TotalUses, PA Review

IniRead, UseageCount, UseageLog.ini, %A_UserName%, Total Uses
UseageCount++
Iniwrite, %UseageCount%, UseageLog.ini, %A_UserName%, Total Uses

IniRead, UseageCount, UseageLog.ini, %A_UserName%, PA Review
UseageCount++
Iniwrite, %UseageCount%, UseageLog.ini, %A_UserName%, PA Review

In the above example, the data items that change from snippet to snippet (marked in red) turn into the parameters inside the parentheses. When writing the function, place the repeated command code between curly brackets { and } after the function name and contiguous parentheses like so:

FunctionLog(Parameter1,Parameter2)
{
  IniRead, UseageCount, UseageLog.ini, %Parameter1%, %Parameter2%
  UseageCount++
  Iniwrite, %UseageCount%, UseageLog.ini, %Parameter1%, %Parameter2%
}

I named the function FunctionLog() but you can use any name you prefer—such as UsageLogging() or UsageLog().

Assigning Names to the Parameters

To make remembering what each parameter represents easier, I replaced then with the names Section and Key—directly from the AutoHotkey documentation for the IniRead command:

FunctionLog(Section,Key)
{
  IniRead, UseageCount, UseageLog.ini, %Section%, %Key%
  UseageCount++
  Iniwrite, %UseageCount%, UseageLog.ini, %Section%, %Key%
}

The parameter names (Section and Key) create the Local variables used within the function. It’s important to remember that Local variables only exist within the function itself. If you want to use a value from a function elsewhere or apply a variable created outside the function, then you need to make it a Global variable.

Calling the Function

Use (or call) the function by simply inserting the function name with the appropriate data items within the parentheses into your AutoHotkey script:

FunctionLog("TotalUses","Count")
FunctionLog("TotalUses","PA Review")
FunctionLog(A_UserName,"Total Uses")
FunctionLog(A_UserName,"PA Review")

When placing parameter data inside the parentheses, the function looks for those variable names. Therefore, when passing plain text, enclose it in double-quotes as shown. Note that we do not enclose the variable A_UserName in quotes. As a built-in AutoHotkey variable, it contains the Windows username.

The final form of the function calls replaces the redundant code in your example as follows:

; TOTAL USEAGE COUNT (GLOBAL COUNT OF ALL NOTES)
 FunctionLog("TotalUses","Count")

; TOTAL USEAGE COUNT BY NOTE TYPE
 FunctionLog("TotalUses","PA Review")

; TOTAL USEAGE COUNT BY USER
 FunctionLog(A_UserName,"Total Uses")

; TOTAL USEAGE COUNT BY NOTE TYPE FOR EACH USER
 FunctionLog(A_UserName,"PA Review")

; DATE/TIME/USER/NOTE TYPE
 UseDate = %A_DD%/%A_MM%/%A_YYYY%
 UseTime = %A_Hour%:%A_Min%
 FileAppend, %UseDate% %UseTime% %A_UserName% (PA Review)`n
     ,UserTimeDate.log

FunctionLog(Section,Key)
 {
 IniRead, UseageCount, UseageLog.ini, %Section%, %Key%
 UseageCount++
 iniwrite, %UseageCount%, UseageLog.ini, %Section%, %Key%
 }

You can place the defined functions (the function name, parentheses enclosing parameters—if any—with curly bracket enclosed command code) almost anywhere in the AutoHotkey script, but you may find it cleaner to group functions toward the end of the file. Then, if you use numerous different functions, you’ll easily find them. You may also save them in a separate file and use the #Include directive to load all of your functions upon the first loading of the script.

Alternative Method for Calling a Function

AutoHotkey can also call a function by setting a variable equal to its output:

Test := returnTest()
MsgBox % Test

returnTest() {
  return 123
}

In this example, taken from the AutoHotkey Web site, the function returnTest() assigns (returns) the value 123 to the variable Test. However, since the FunctionLog() function we’ve written does not return a value, we don’t need this approach in our example—although it would continue work (i.e. Test := FunctionLog(“TotalUses”,”Count”) saving no value to the variable Test).

Making a Function More Universal

You can expand the flexibility of the function by adding more parameters. For example, what if you want to use it with various INI files? Merely add another parameter inside the parentheses for the filename:

FunctionLog(Section,Key,Filename)
{
  IniRead, UseageCount, %Filename%, %Section%, %Key%
  UseageCount++
  Iniwrite, %UseageCount%, %Filename%, %Section%, %Key%
}

Then, you could use the function with any INI file by merely supplying the filename when calling the function:

FunctionLog("TotalUses","Count","UseageLog1.ini")
FunctionLog("TotalUses","PA Review","UseageLog1.ini")
FunctionLog(A_UserName,"Total Uses","UseageLog2.ini")
FunctionLog(A_UserName,"PA Review","UseageLog2.ini")

The above function calls use two separate files UseageLog1.ini and UseageLog2.ini.

*          *          *

Cover 200 BorderFor more information about how functions work and where to use them, see Chapter Four “Functions as Building Blocks” of Beginning Tips for Writing AutoHotkey Scripts. Not only does the book contain more information about how to use AutoHotkey functions but it offers numerous other AutoHotkey scripting tips—including script structure and a framework for how AutoHotkey scripts work.

Check out the AutoHotkey Library Bundles for deep discounts on Jack’s books!

*          *          *

Gary, when reviewed the rest of your script, I noticed a couple of things that I might do a little differently. There is nothing wrong with the way you wrote it because it works, but these tips may make things a little easier in the long run.

Terminate Your Labels with a Return

Tip Number One: I noticed that you used a number of Label subroutines not terminated with the Return command. At the end of your ButtonSubmit: subroutine:

; DATE/TIME/USER/NOTE TYPE
 UseDate = %A_DD%/%A_MM%/%A_YYYY%
 UseTime = %A_Hour%:%A_Min%
 FileAppend
    ,%UseDate% %A_Space% %UseTime% %A_Space% %A_UserName% %A_Space% (PA Review)`n
    ,UserTimeDate.log

GuiClose:
 ExitApp

ButtonCancel:
 ExitApp

As a general practice, enclose your Label subroutines by adding the Return—making each a separate entity. This forms self-contained, more portable modules. Otherwise, the following subroutines always run whenever the previous Label drops-through the next Label. (You might find times when you want to deliberately drop-through, but often a missing Return can cause surprising behavior.) In your situation, it makes no difference since they all exit the script (ExitApp), but I would find the following much cleaner and easy for later adaptation:

; DATE/TIME/USER/NOTE TYPE
UseDate = %A_DD%/%A_MM%/%A_YYYY%
UseTime = %A_Hour%:%A_Min%
FileAppend, %UseDate% %UseTime% %A_UserName% (PA Review)`n,UserTimeDate.log

ExitApp
Return

; Runs when the little x in the upper right-hand corner is clicked.
GuiClose:               
  ExitApp
Return

ButtonCancel:   ;Runs when the Cancel button is clicked.
  ExitApp
Return

Now, you can place the GuiClose: and ButtonCancel: subroutines anywhere in the AutoHotkey script after the auto-execute section—as long as it doesn’t appear inside Another Label subroutine. Notice that I placed another ExitApp inside your original subroutine and added a Return.

Using A_Space

Tip Number Two: The following line:

FileAppend
    ,%UseDate% %A_Space% %UseTime% %A_Space% %A_UserName% %A_Space% (PA Review)`n
    ,UserTimeDate.log

can be shortened to:

FileAppend, %UseDate%   %UseTime%   %A_UserName%   (PA Review)`n
    ,UserTimeDate.log

You only need the A_Space notation for a space character at the beginning of a text line. AutoHotkey usually ignores extra keyboard spaces. Otherwise, the command would retain each as a space character in a line of text.

*          *          *

Note for new readers: If you’re new to AutoHotkey, you may want to check out this Introduction to AutoHotkey: A Review and Guide for Beginners.

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

Buy Jack a Cappuccino!

If you found a blog particularly useful, show your appreciation by buying Jack a cup of coffee! Or, if you prefer something for yourself, check out his books.

$4.95

Peruse my AutoHotkey books at ComputorEdge E-Books!

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 )

Facebook photo

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

Connecting to %s