Using Prototyping in AutoHotKey: A Tool for Scripting In AutoHotKey (AHK) v2
In the realm of AutoHotKey (AHK) v2, developers are presented with a potent tool—prototyping—that transforms the scripting landscape by allowing dynamic assignment of methods to objects. Unlike traditional programming languages, Prototyping lets developers assign methods to objects dynamically, which is not typically seen in conventional programming languages. Prototyping in AHK allows developers to add their own custom methods during runtime and incorporate features from different programming languages into their AHK scripts.
Prototyping makes it easier to expand the functionality of AHK scripts. It works as a dynamic framework that helps with the addition of methods to objects in real-time. This becomes especially important when developers want to integrate functions similar to those in other programming languages, serving as a link for feature integration across languages.
In the developer's toolkit, prototyping is a flexible tool to improve script adaptability. It uses descriptors, configurations that describe how properties should be used, to combine methods in a straightforward way. This approach streamlines the development process and allows developers to merge desired features from different programming languages into their AHK scripts.
Pardon my eagerness, but prototyping in AutoHotKey is more than just an additional feature. It's a practical approach for developers interested in adding personalized features to their scripts. By using prototyping, scripts aren't limited by set methods, but can be built using a mix of functionalities from various languages. As developers start to use the prototyping feature, they'll find they can customize their scripts more effectively in AHK.
In summary, prototyping is a key feature in AHK scripting that enables scriptwriters to build adaptable and modular code structures. Its dynamic nature allows for changes to be made in real-time, which increases code reusability and simplifies complex operations. As scriptwriters use prototyping, they'll see an improvement in their scripting skills, resulting in more efficient and easier-to-maintain codebases.
Examples
Example 1: Groggy's Explainer - Adding the 'Contains()' method to Arrays. Akin to my_list.contains('string') => index pos in python
The script defines a custom method "Contains" for arrays, allowing users to check if a specific item is present. This method is added to the prototype of the Array class, making it accessible to all array instances. The array_contains function is responsible for the actual check, considering case sensitivity if specified. Examples with an array of fruits demonstrate how to use the custom method to determine if a particular item exists in the array. The comments have been simplified to provide clear explanations of each step.
Code: Select all
/*
You can add any property to any object
This allows you to access the property at any time
However, in this case, we want to add it to the object's prototype.
This gives all instances of the object access to the new property
If you don't add it to the prototype, it won't be in the created objects, only the base object.
You'll want to create a descriptor. It's a special object that allows a property to be called
(Spoiler alert, there are no TRUE "methods" in AHK.
They're ALL properties with callable descriptors.)
Descriptor object example: {Call:array_contains}
The descriptor "describes" what actions a property should take
*/
Array.Prototype.DefineProp("Contains", {Call:array_contains})
; When a descriptor is called, it ALWAYS sends a reference to the object as the first param.
; It's known as the "this" variable when working inside an object/class/etc.
; The search param is the expected item to find.
; CaseSense is a bonus option added just to make the method more robust
array_contains(arr, search, casesense:=0) {
for index, value in arr {
if !IsSet(value)
continue
else if (value == search)
return index
else if (value = search && !casesense)
return index
}
return 0
}
; Define an array
arr := ['apple', 'banana', 'cherry']
; Now you can use the new "contains" method we just added
; Remember, the array itself is passed implicitly and doesn't need to be included.
; The "search" param is required and not providing one throws a parameter error
; "CaseSense" is optional and defaults to false
if arr.contains('Cherry')
MsgBox('Found it!')
else MsgBox('Not found.')
; Trying again, except this time using case sensitivity
if arr.contains('Cherry', 1)
MsgBox('Found it!')
else MsgBox('Not found.')
Prototyping enhances script flexibility. Consider a scenario where you want to add a custom method to handle GUI elements.
Code: Select all
; Define a prototype method for GUI elements
GuiControl.Prototype.DefineProp("CustomAction", {Call: custom_action})
; Prototype method implementation
custom_action(control) {
; Your custom action logic here
}
Code: Select all
; Define a prototype method with a descriptor
Object.Prototype.DefineProp("CustomMethod", {
Call: custom_method
})
; Prototype method implementation
custom_method(CallingObject, param1, param2) {
; Your custom method logic here
}
Code: Select all
; This method is added to the prototype of the Map class to retrieve an array of keys.
Map.Prototype.DefineProp("Keys", { Call: get_keys })
; M := Map("Key1", "Value1", "Key2", "Value2", "Key3", "Value3")
; M.Keys() ; returns ["Key1", "Key2", "Key3"]
; now keys and values can be immediately looped like this:
; for arr in myMap.Keys() {}
get_keys(mp) {
mapKeys := []
for k, v in mp {
if !IsSet(k)
continue
else if k is string or k is number
mapKeys.Push(k)
}
return mapKeys
}
; This method is added to the prototype of the Map class to retrieve an array of string values.
Map.Prototype.DefineProp("Values", { Call: get_values })
get_values(mp) {
mapValues := []
for k, v in mp {
if !IsSet(v)
continue
else
mapValues.Push(v)
}
return mapValues
}
Example 4:
Code: Select all
/*
Class: get_row
Description: Represents a method to get the focused row of a ListView control.
Methods:
- Call(LV): Retrieves the focused row of the ListView control.
Parameters:
- LV: The ListView control.
Returns:
- An array containing the values of the focused row.
Example usage:
LV.GetRow() ; LV is an instance of Gui.Listview
Returns: ["Value1", "Value2", "Value3", ...]
*/
Gui.Listview.Prototype.DefineProp("GetRow", { Call: get_row })
; define the prototype
get_row(LV)
{
if not LV.Focused
return 0
FocusedRow := []
Loop LV.GetCount("Column")
{
FocusedRow.Push(LV.GetText(LV.GetNext(), A_Index))
}
return FocusedRow
}
Code: Select all
/*
Class: set_cell
Description:
This class provides a static method to set the value of a cell in a ListView control.
Methods:
- Call(LV, row, col, value): Sets the value of the specified cell in the ListView control.
Parameters:
- row (integer): The row index of the cell.
- col (integer): The column index of the cell.
- value (string): The value to set in the cell.
Example usage:
```
LV := Gui.Add("ListView")
LV.SetCell(1, 2, "New Value")
```
*/
Gui.Listview.Prototype.DefineProp("SetCell", { Call: set_cell })
class set_cell
{
static Call(LV, row, col, value)
{
LV.Modify(row, "Col" col, value)
}
}
Code: Select all
#Requires AutoHotkey v2
setterValue := 0
Obj := {}
; Helper Function for 'get'
getFunction(this) {
return this._hiddenValue ; Return the internally stored value
}
; Helper Function for 'set'
setFunction(this, value) {
this._hiddenValue := value ; Update the internally stored value
}
; Create an object and a hidden field to store the property value
myObject := { _hiddenValue: "" }
; Define a dynamic property with both getter and setter
myObject.DefineProp("DynamicProperty", {
Get: getFunction,
Set: setFunction
})
; Now you can get and set the value of DynamicProperty
myObject.DynamicProperty := "Hello, World" ; Setter is called
MsgBox myObject.DynamicProperty ; Getter is called and displays "Hello, World"