Writing AutoHotkey Functions to Make Life Easier

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

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. Life can be made simpler (and easier) by writing functions to replace repetitious code. That is often the purpose of functions, although there are a variety of ways to use them. The beauty of a function is that once it is written it can be used virtually anywhere over and over again without rewriting the code.

*          *          *

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.

*          *          *

A user-defined function has two parts. First is the name of the function which is used when running (or calling) it. Second is the parameters section which is located within the set of parentheses:

FunctionName(Parameter1, Parameter2, Parameter3,…)

The function name is defined (or assigned) when you write the function. A function must include the set of parentheses which encloses the data sent to the function for processing. There can be any number of parameters or none at all, but the number of data items (separated by commas) must match the number of parameters defined in the function itself. When the function is called, the data items must be in the same order in the calling function as they are in the function itself.

Here is the example that you sent me of code you would like to turn into a function:

; ***********************************************
 ; * *
 ; * *
 ; * 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 snippets in this example which are repetitive. If you find that you are writing a lot of the same code (probably a cut-and-paste with modifications), that’s when you want to consider writing a function. The last snippet in your example, “DATE/TIME/USER/NOTE TYPE”, is unique and probably not worth turning into a function unless there are other places in your script where you need to do the same thing.

Creating an AutoHotkey Function to Replace Repetitious Code

The first step in writing a user-defined function in AutoHotkey is separating the repetitive code and the unique code. The repeated code will become the basis for the script within the function and the unique data items will become the parameters passed to the function from 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 which change from snippet to snippet are marked in red and will become the parameters inside the parentheses. When writing the function the repeated code is placed 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%
}

The function name FunctionLog() could be any other name you prefer such as UsageLogging() or UsageLog().

Assigning Names to the Parameters

To make it easier to remember what each parameter represents, I replaced them 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) are the variables which are used within the function. It’s important to remember that they are local variables which only exist within the function when it runs. If you want to use a value for a function elsewhere, then you will need to save it to a global variable, usually created outside the function.

Calling the Function

Using the function is as simple as inserting into your AutoHotkey script the function name with the appropriate data items within the parentheses:

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 is looking for a variable. Therefore, when passing text, enclose it in double quotes as shown. Note that A_UserName is not enclosed in quotes because it is a built-in AutoHotkey variable name which contains the Windows username.

The final form of the function calls which replace the redundant code in your example is 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%
 }

The defined function (function name with enclosed code) can be located almost anywhere in the AutoHotkey script, but it is cleaner to group them toward the end of the file. That way, if you use a lot of different functions, they are easier to find. You may also keep them in a separate file and use the #Include statement to load all of your functions when first running the script.

Alternative Way to Call a Function

A function can also be called 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. Since the FunctionLog() function we’ve written does not return a value, this notation is not necessary in our example—although it would still work (i.e. Test := FunctionLog(“TotalUses”,”Count”) with no value being saved to the variable Test).

Making a Function More Universal

The flexibility of the function can be expanded by adding more parameters. For example, what if you want to use it with various INI files, add another parameter for the filename:

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

Then, the function could be used with any INI file by merely supplying the filename when the function is called:

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 uses two separate files UseageLog1.ini and UseageLog2.ini.

*          *          *

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 have 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 have a number of labels (subroutines) which are not terminated with the Return command. At the end of your ButtonSubmit: label:

; 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, it is a good idea to enclose your labels and make them separate entities by adding the Return. This makes each self-contained and more portable. Otherwise, the subroutine will always run whenever the previous label runs. (There may be times when you want to deliberately do this, but often it 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

This way the GuiClose: label and ButtonCancel: label can be placed anywhere in the AutoHotkey script after the auto-execute section as long as it isn’t inside another label. 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

The A_Space notation for a space is only necessary at the beginning of a text line where AutoHotkey usually ignores extra keyboard spaces. Otherwise, each space will be retained 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.

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s