Skip to content

Instantly share code, notes, and snippets.

@G33kDude
Last active August 29, 2015 14:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save G33kDude/21345408c07d1181f111 to your computer and use it in GitHub Desktop.
Save G33kDude/21345408c07d1181f111 to your computer and use it in GitHub Desktop.
This [strike]"tutorial"[/strike]/explanation requires the knowledge of how objects (aka arrays) work in AutoHotkey. If I got something wrong, please point it out.
[hr][/hr]
[center][b]What is a class?[/b][/center]
[quote="wiktionary"]class /klɑːs/
n. A group, collection, category or set sharing characteristics or attributes.[/quote]
In the case of AutHotkey, most commonly an object. In this post, we will be talking about the syntax of making custom object classes that have methods, and how it works.
[hr][/hr]
[center][b]Understanding function references[/b][/center]
Lets start off with the concept of functions in AutoHotkey. Originally in AutoHotkey, functions were accessible only by using their name. However, Lexikos got his hands on this and added a brand new way of accessing functions. This way, called [i]Function References[/i] meant that you can save a (representation of a) function to a variable, and use that variable in place of the function name. To get a function reference, the new (actually pretty old now) [c]MyReference := Func("MyFunctionName")[/c] syntax must be used. To call a function using a reference, [c]RetVal := MyReference.Call([i]Params...[/i])[/c] or [c]RetVal := %MyReference%([i]Params...[/i])[/c] must be used, though the latter is preferred since it also supports function names in addition to references.
[code=autohotkey file=Script.ahk]MyReference := Func("MyMsgBox")
%MyReference%("Hello World!")
MyMsgBox(MyString)
{
MsgBox, % MyString
}[/code]
A nice "side-effect" (see: intentional) of this is that not only can function references be stored in variables, but they can also be stored in [i]objects[/i]. Suddenly, you can collect all your related functions into a single object, [c]MyObj := {Key: Func("MyMsgBox")}[/c] and [c]MyObj.Fish := Func("GoFishing")[/c]. After storing your function references in an object, you can call them using [c]MyObj.Key.Call("Hello World!")[/c] and [c]MyObj.Fish.Call()[/c].
[code=autohotkey file=Script.ahk]MyObj := {Key: Func("MyMsgBox")}
MyObj.Fish := Func("GoFishing")
Tmp := MyObj.Key, %Tmp%("Brb, Going fishin'")
; or MyObj.Key.Call("Brb, going fishin'")
MyObj.Fish.Call()
GoFishing()
{
; Lock the screen, we're going fishing
DllCall("LockWorkStation")
}
MyMsgBox(MyString)
{
MsgBox, % MyString
}[/code]
There are other ways of calling function references (especially from objects), and one of these is [c]MyObj.Key([i]Params...[/i])[/c] (I removed [c].Call[/c]]). This passes in the object containing the function reference as the first parameter, shifting all the others over. Now your function (if set up with the correct number of parameters) knows in what context it is being called from, allowing it to access other things in the object. If you had the code [c]MyObj := {MyMethod: Func("MyFunction"), Apples: 5}[/c], MyFunction could take in its first parameter as the variable [c]this[/c] and do [c]MsgBox, % "Johnny has " this.Apples " apples"[/c].
[code=autohotkey file=Script.ahk]MyObj := {MyMethod: Func("MyFunction"), Apples: 5}
MyObj.MyMethod("Johnny", 123)
MyFunction(this, Param1, Param2)
{
MsgBox, % Param1 " has " this.Apples " apples for " Param2 " customers"
}[/code]
In this code [c]MyObj[/c] is what is known in the documentation as a [i]prototype object[/i] or [i]base object[/i]. Base/Prototype objects are what "class" instances are created from. Creating a base object like this can get unwieldy with size, but luckily there is a better way. The following is effectively the equivalent of previous example:
[code=autohotkey file=Script.ahk]MyObj.MyMethod("Johnny", 123)
class MyObj
{
static Apples := 5
MyMethod(Param1, Param2)
{
MsgBox, % Param1 " has " this.Apples " apples for " Param2 " customers"
}
}[/code]
There are a few key differences in the second example that are important to notice. MyObj is automatically created and made into a [i][url=http://ahkscript.org/docs/Functions.htm#SuperGlobal]super-global variable[/url][/i] regardless of where in the code it's defined. Also, the first parameter of the method ([c]this[/c]) is added behind the scenes so you don't have to define it explicitly for every method. Finally, [c]MyMethod[/c] never exists as a separate function [c]MyFunction[/c].
[hr][/hr]
[center][b]Understanding class instances (and the [c]new[/c] keyword).[/b][/center]
An instance of a class is just another object, with its [c]base[/c] attribute set to the prototype (base) object. For example, you can create an instance with the code [c]Instance := {base: MyClass}[/c] (though this doesn't call the instantiation routines). When the "base" attribute is set to something, AutoHotkey knows to pull information from there if it doesn't already exist in the instance. This applies to calling methods and retrieving attribute values, but not to setting attribute values.
The keyword [c]new[/c] is used to easily create an instance of a class/base object, as well as automatically instantiate it. I'm not completely sure how it works internally, but here's to the best of my knowledge. It starts off by creating a new object as in the previous paragraph. Then it calls the ([b]reserved by AutoHotkey, do not define it yourself[/b]) [c]__Init()[/c] meta-function on it which handles things like non-static class variables. Once it finishes with that it checks for and calls the user defined [c]__New[/c] initiation [url=http://ahkscript.org/docs/Objects.htm#Meta_Functions]meta-function[/url]. If it exists and there is a return value, that is what is returned by [c]new[/c]. If it doesn't exist or there isn't a return value, [c]new[/c] returns the new instance.
Here is some code demonstrating the [c]__New[/c] meta-function:
[code=autohotkey file=Script.ahk]MyInstance := new MyClass("MyDefault")
MyInstance.MyMethod()
class MyClass
{
__New(Default)
{
InputBox, OutputVar,, Enter a value for your new attribute,,,,,,,, %Default%
this.Attribute := OutputVar
}
MyMethod()
{
MsgBox, % this.Attribute
}
}[/code]
[hr][/hr]
[center][b]Understanding inheritance[/b][/center]
When AutoHotkey goes to find a attribute in your object, it first does the meta-function [c]object.base.__Get[/c] check, then it moves on to "Does attribute exist in the object?". If it does, it uses it. If not it checks "Does it exist in the object's base?". If it does, it uses it. If not, it checks the base's base, and then the base's base's base, and so on until there are no more base objects.
When AutoHotkey goes to set a attribute in your object, it just [i]creates[/i] the attribute in the original object. Consider closely what the following code will do:
[code=autohotkey file=Script.ahk]x := new y
MsgBox, % x.Attribute
y.Attribute := 3
MsgBox, % x.Attribute
x.Attribute -= 1
y.Attribute := 7
MsgBox, % x.Attribute
class Y
{
static Attribute := 5
}[/code]
On line one, it creates an instance of y (think [c]x := {base: y}[/c]). On line two, it tries to get [c]x.Attribute[/c]. Seeing that [c]x[/c] doesn't have a [c]Attribute[/c], and that [c]x.base[/c] does, it returns [c]x.base.Attribute[/c] (5) instead. On line three, it changes [c]y.Attribute[/c] to [c]3[/c]. [c]x[/c] still doesn't have a [c]Attribute[/c], and [c]y[/c] is still the base of [c]x[/c], so on line 4 it sees when it sees that [c]x[/c] doesn't have a [c]Attribute[/c] it returns [c]x.base.Attribute[/c] (aka [c]y.Attribute[/c], with the value of 3) instead. On line 5 it pulls the value of [c]x.Attribute[/c], but sees that it doesn't have a [c]Attribute[/c] and instead uses [c]x.base.Attribute[/c], then it subtracts 1 from that and [i]gives[/i] [c]x[/c] a [c]Attribute[/c] of [c]2[/c]. Since [c]x[/c] now has a [c]Attribute[/c], any further changes to [c]y.Attribute[/c] are not inherited, and the value [c]2[/c] will be shown on line 7.
[hr][/hr]
[center][b]How meta-functions work[/b][/center]
Two sections ago I explained the use of the [c]new[/c] keyword, and by extension the [c]__New[/c] [i]meta-function[/i]. A meta-function is a class method that doesn't get explicitly called, but gets called when something happens to the object. Meta-functions are called [i]from the base object[/i] not the instance. If you create an object [c]{__Call: Func("MyCallA"), base: {__Call: Func("MyCallB")}}[/c], [c]MyCallB[/c] would be used as the meta-function. Note that inheritance applies to meta-functions, so when [c]object.base.Meta[/c] doesn't exist (or returns nothing), it checks for [c]object.base.base.Meta[/c], then [c]object.base.base.base.Meta[/c] and so on.
There are several meta-functions, and they're listed below along with the context in which they are called:
[list]
[*][c]__New([i]Params...[/i])[/c] - This gets called as [c]MyObject.base.__Delete.(MyObject, "Banana", "Sandwich")[/c] when an an instance is created using [c]new BaseObject("Banana", "Sandwich")[/c].
[*][c]__Delete()[/c] - This gets called as [c]MyObject.base.__Delete.(MyObject)[/c] when the instance gets deleted, such as at the end of the script/function or when you do this to the last reference: [c]MyInstance := AnythingElse[/c]
[*][c]__Get([i]Key[/i])[/c] - This gets called as [c]MyObject.base.__Get.(MyObject, "Pizza")[/c] when something does [c]Var := MyObject.Pizza[/c] or [c]Var := MyObject["Pizza"][/c]
[*][c]__Set([i]Key[/i], [i]Value[/i])[/c] - This gets called as [c]MyObject.base.__Set.(MyObject, "Pizza", "Cheez")[/c] when something does [c]MyObject.Pizza := "Cheez"[/c] or [c]MyObject["Pizza"] := "Cheez"[/c]
[*][c]__Call([i]Name[/i], [i]Params...[/i])[/c] - This gets called as [c]MyObject.base.__Set.(MyObject, "DoSomething", "P1", "P2", "P3")[/c] when something does [c]MyObject.DoSomething("P1", "P2", "P3")[/c]. Note that you can pass an arbitrary number of parameters to it. You can handle this using Variadic Functions, but that is outside the scope of this explanation.
[*][c]__Init()[/c] - This is reserved by AutoHotkey and should never be defined.[/list]
[hr][/hr]
--- More coming ---
[hr][/hr]
See post source bbcode and revisions here:
[spoiler][gist]21345408c07d1181f111[/gist][/spoiler]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment