Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
guides:objects [2025-03-31 14:44] – [Fundamentals of Class Objects] geek | guides:objects [2025-03-31 17:28] (current) – Move classes header to separate page geek | ||
---|---|---|---|
Line 328: | Line 328: | ||
===== Classes ===== | ===== Classes ===== | ||
- | In AutoHotkey, classes are a way to design a template for new objects | + | See: [[guides:objects:classes|]] |
- | Imagine you're writing a script where you're working extensively with X/Y coordinates. All over, you may have object definitions like '' | ||
- | |||
- | <runner ahk2> | ||
- | /** | ||
- | * Define a class object: a template for instance objects to be made from. | ||
- | * Represents an X/Y coordinate pair. | ||
- | */ | ||
- | class Coord { | ||
- | x := 0 | ||
- | y := 0 | ||
- | } | ||
- | |||
- | /** | ||
- | * Gets the cursor position | ||
- | * | ||
- | * @return {Coord} The coordinate pair corresponding to the cursor | ||
- | */ | ||
- | MyMousePosGetter() { | ||
- | MouseGetPos &x, &y | ||
- | |||
- | ; Create an instance object, modeled after the class object. | ||
- | ; Like AHK's built-in `Object()`. | ||
- | pair := Coord() | ||
- | |||
- | pair.x := x | ||
- | pair.y := y | ||
- | return pair | ||
- | } | ||
- | |||
- | pos := MyMousePosGetter() | ||
- | MsgBox pos.x "," | ||
- | </ | ||
- | |||
- | With this new Coord class, functions which return a coordinate pair can be more useful to work with than ones returning a basic object. If you're using a supported editor like Visual Studio Code, when typing code such as that last line '' | ||
- | |||
- | Now, you might notice that we've gone from a simple one line definition of returning a basic object to now four full lines of creating the object from the template, filling its properties, and returning the object. This explosion of size can be managed handily through a concept known as // | ||
- | |||
- | <runner ahk2> | ||
- | /** | ||
- | * Define a class object: a template for instance objects to be made from. | ||
- | * Represents an X/Y coordinate pair. | ||
- | */ | ||
- | class Coord { | ||
- | x := 0 | ||
- | y := 0 | ||
- | | ||
- | /** | ||
- | * Constructor for the Coord object, takes arguments x and y | ||
- | * and populates the x and y properties of the object. | ||
- | */ | ||
- | __New(x, y) { | ||
- | this.x := x | ||
- | this.y := y | ||
- | } | ||
- | } | ||
- | |||
- | /** | ||
- | * Gets the cursor position | ||
- | * | ||
- | * @return {Coord} The coordinate pair corresponding to the cursor | ||
- | */ | ||
- | MyMousePosGetter() { | ||
- | MouseGetPos &x, &y | ||
- | return Coord(x, y) | ||
- | } | ||
- | |||
- | pos := MyMousePosGetter() | ||
- | MsgBox pos.x "," | ||
- | </ | ||
- | |||
- | This simple addition, a function inside the class definition, exposes the second great strength of classes. Instead of just containing data, they can also specify the functions that pertain to that data. For example, we could add a function like '' | ||
- | |||
- | <runner ahk2> | ||
- | /** | ||
- | * Define a class object: a template for instance objects to be made from. | ||
- | * Represents an X/Y coordinate pair. | ||
- | */ | ||
- | class Coord { | ||
- | x := 0 | ||
- | y := 0 | ||
- | | ||
- | /** | ||
- | * Constructor for the Coord object, takes arguments x and y | ||
- | * and populates the x and y properties of the object. | ||
- | */ | ||
- | __New(x, y) { | ||
- | this.x := x | ||
- | this.y := y | ||
- | } | ||
- | |||
- | /** | ||
- | * Returns a new coordinate with its positions holding the relative | ||
- | * values between this coordinate and the target coordinate. | ||
- | */ | ||
- | RelativeTo(targetCoord) { | ||
- | return Coord(this.x - targetCoord.x, | ||
- | } | ||
- | } | ||
- | |||
- | /** | ||
- | * Gets the cursor position | ||
- | * | ||
- | * @return {Coord} The coordinate pair corresponding to the cursor | ||
- | */ | ||
- | MyMousePosGetter() { | ||
- | MouseGetPos &x, &y | ||
- | return Coord(x, y) | ||
- | } | ||
- | |||
- | /** | ||
- | * Gets the position of the given window | ||
- | * @return {Coord} The coordinate pair corresponding to the window position | ||
- | */ | ||
- | MyWinPosGetter(title) { | ||
- | WinGetPos &x, &y, &w, &h, title | ||
- | return Coord(x, y) | ||
- | } | ||
- | |||
- | ; Get the active window' | ||
- | WinMove 30, 40, 50, 60, " | ||
- | winPos := MyWinPosGetter(" | ||
- | |||
- | ; Get the mouse position | ||
- | MouseMove 123, 456 | ||
- | mousePos := MyMousePosGetter() | ||
- | |||
- | MsgBox " | ||
- | MsgBox " | ||
- | |||
- | relativePos := mousePos.RelativeTo(winPos) | ||
- | MsgBox " | ||
- | </ | ||
- | |||
- | Alongside methods that can be called to manipulate the object' | ||
- | |||
- | <runner ahk2> | ||
- | /** | ||
- | * Define a class object: a template for instance objects to be made from. | ||
- | * Represents an X/Y coordinate pair. | ||
- | */ | ||
- | class Coord { | ||
- | x := 0 | ||
- | y := 0 | ||
- | | ||
- | /** | ||
- | * Constructor for the Coord object, takes arguments x and y | ||
- | * and populates the x and y properties of the object. | ||
- | */ | ||
- | __New(x, y) { | ||
- | this.x := x | ||
- | this.y := y | ||
- | } | ||
- | |||
- | /** | ||
- | * Returns a new coordinate with its positions holding the relative | ||
- | * values between this coordinate and the target coordinate. | ||
- | */ | ||
- | RelativeTo(targetCoord) { | ||
- | return Coord(this.x - targetCoord.x, | ||
- | } | ||
- | | ||
- | /** | ||
- | * Gets the cursor position | ||
- | * | ||
- | * @return {Coord} The coordinate pair corresponding to the cursor | ||
- | */ | ||
- | MyMousePosGetter() { | ||
- | MouseGetPos &x, &y | ||
- | return Coord(x, y) | ||
- | } | ||
- | |||
- | /** | ||
- | * Gets the position of the given window | ||
- | * @return {Coord} The coordinate pair corresponding to the window position | ||
- | */ | ||
- | MyWinPosGetter(title) { | ||
- | WinGetPos &x, &y, &w, &h, title | ||
- | return Coord(x, y) | ||
- | } | ||
- | } | ||
- | |||
- | ; Get the active window' | ||
- | WinMove 30, 40, 50, 60, " | ||
- | winPos := MyWinPosGetter(" | ||
- | |||
- | ; Get the mouse position | ||
- | MouseMove 123, 456 | ||
- | mousePos := MyMousePosGetter() | ||
- | |||
- | MsgBox " | ||
- | MsgBox " | ||
- | |||
- | relativePos := mousePos.RelativeTo(winPos) | ||
- | MsgBox " | ||
- | </ | ||
- | ==== Function Objects ==== | ||
- | |||
- | In AutoHotkey v1, there were several global collections of names that were kept separate. There was a collection of command names, a collection of label names, a collection of function names, and a collection of variable names. AutoHotkey v2 has mostly merged these collections, | ||
- | |||
- | Allowing functions to be saved inside variables and passed around like data is known as having [[https:// | ||
- | |||
- | <runner ahk2> | ||
- | MyFunction() { | ||
- | ; No matter how this function is called, the message box | ||
- | ; will say "You called MyFunction" | ||
- | MsgBox "You called " A_ThisFunc | ||
- | } | ||
- | |||
- | MsgBox IsObject(MsgBox) ", " Type(MsgBox) | ||
- | MsgBox IsObject(MyFunction) ", " Type(MyFunction) | ||
- | |||
- | MyVar := MyFunction ; Put MyFunction into a different variable | ||
- | MyVar() ; Call the function object stored inside MyVar | ||
- | </ | ||
- | |||
- | Function objects come inside global read-only variables by default, but can be passed around just like any other object. As shown above, it's easy to put the function object into a different variable even if the new variable has a different name. Additionally, | ||
- | |||
- | <runner ahk2> | ||
- | MyVar := () => MsgBox(" | ||
- | MyVar() | ||
- | |||
- | ; In AHKv2.1 this is allowed as well: | ||
- | ;MyVar2 := () { | ||
- | ; MsgBox "You called '" | ||
- | ;} | ||
- | ;MyVar2() | ||
- | </ | ||
- | |||
- | By itself, this syntax is usually seen when defining OnEvent type callbacks. It allows you to skip defining a function that might only be called in one place: | ||
- | |||
- | <code autohotkey> | ||
- | CloseCallback() { | ||
- | MsgBox "You tried to close the GUI" | ||
- | } | ||
- | g := Gui() | ||
- | g.OnEvent(" | ||
- | |||
- | ; Can be rewritten as: | ||
- | |||
- | g := Gui() | ||
- | g.OnEvent(" | ||
- | |||
- | ; Or in v2.1: | ||
- | |||
- | g := Gui() | ||
- | g.OnEvent(" | ||
- | MsgBox "You tried to close the GUI" | ||
- | }) | ||
- | </ | ||
- | |||
- | However, where things start to get really interesting is when you put function objects into //other objects//. Just like a function object can be stored inside a regular variable and then that variable becomes callable, a function object can be stored as an object property and then that property becomes callable. A callable property on an object is called a //method//. | ||
- | |||
- | When you call a function stored as an object property, AutoHotkey does a little trick with the parameter list. If you have '' | ||
- | |||
- | <runner ahk2> | ||
- | MyFunction(this, | ||
- | MsgBox 'a: ' a '`nb: ' b '`nc: ' c | ||
- | } | ||
- | |||
- | MyObject := { | ||
- | FunctionProperty: | ||
- | } | ||
- | |||
- | ; These following 4 lines are all equivalent | ||
- | MyObject.FunctionProperty(1, | ||
- | Temp := MyObject.FunctionProperty, | ||
- | (MyObject.FunctionProperty)(MyObject, | ||
- | MyObject.FunctionProperty.Call(MyObject, | ||
- | </ | ||
- | |||
- | ==== Prototyping ==== | ||
- | |||
- | AutoHotkey objects are *prototype* based, but AutoHotkey' | ||
- | |||
- | The first part of this arrangement was //function objects// being nested inside regular objects. The second part is // | ||
- | <runner ahk2> | ||
- | baseObject := { | ||
- | someProperty: | ||
- | } | ||
- | |||
- | testObject := { | ||
- | base: baseObject | ||
- | } | ||
- | |||
- | MsgBox testObject.someProperty ; Will show " | ||
- | </ | ||
- | |||
- | ==== Class Syntax ==== | ||
- | |||
- | AutoHotkey' | ||
- | |||
- | Class syntax is used to simultaneously define two things: a " | ||
- | |||
- | Remembering the fundamental of how functions stored in objects are called, it would mean that in this following example, when '' | ||
- | |||
- | <runner ahk2> | ||
- | testMethod(this, | ||
- | MsgBox "this Ptr: " ObjPtr(this) | ||
- | MsgBox 'a: ' a '`nb: ' b '`nc: ' c | ||
- | } | ||
- | MyClass := { | ||
- | Prototype: { | ||
- | | ||
- | } | ||
- | } | ||
- | |||
- | myObject := {base: MyClass.Prototype} | ||
- | |||
- | MsgBox " | ||
- | MsgBox " | ||
- | |||
- | myObject.functionProperty(1, | ||
- | </ | ||
- | |||
- | The "class object" | ||
- | |||
- | An instance factory is a function that creates instances of a class. An instance factory for an AHK class works something like this: | ||
- | |||
- | <code autohotkey> | ||
- | classFactory(someClass) { | ||
- | instance := {base: someClass.Prototype} | ||
- | instance.__Init() | ||
- | if HasMethod(instance, | ||
- | instance.__New() | ||
- | } | ||
- | return instance | ||
- | } | ||
- | </ | ||
- | The instance factory gets put onto the class object as its " | ||
- | <runner ahk2> | ||
- | testMethod(this, | ||
- | MsgBox 'a: ' a '`nb: ' b '`nc: ' c | ||
- | } | ||
- | classFactory(someClass) { | ||
- | instance := {base: someClass.Prototype} | ||
- | instance.__Init() | ||
- | if HasMethod(instance, | ||
- | instance.__New() | ||
- | } | ||
- | return instance | ||
- | } | ||
- | MyClass := { | ||
- | Prototype: { | ||
- | | ||
- | }, | ||
- | Call: classFactory | ||
- | } | ||
- | myInstance := MyClass() | ||
- | myInstance.functionProperty(" | ||
- | </ | ||
- | |||
- | This code invokes " | ||
- | |||
- | That's the vast majority of what class syntax does. In that last example, we manually created this class: | ||
- | |||
- | <runner ahk2> | ||
- | class MyClass { | ||
- | functionProperty(a, | ||
- | MsgBox 'a: ' a '`nb: ' b '`nc: ' c | ||
- | } | ||
- | } | ||
- | myInstance := MyClass() | ||
- | myInstance.functionProperty(" | ||
- | </ | ||
- | |||
- | When defining a class, it allows you to specify static and non-static properties. You can do exactly the same with the manually written code. Static properties get added to the class object. Non-static properties get added to the instance by the '' | ||
- | |||
- | <runner ahk2> | ||
- | class MyClass1 { | ||
- | static someProp := 123 | ||
- | someProp := 456 | ||
- | } | ||
- | myInstance1 := MyClass1() | ||
- | MsgBox " | ||
- | |||
- | ; Equivalent to | ||
- | |||
- | classFactory(someClass) { | ||
- | instance := {base: someClass.Prototype} | ||
- | instance.__Init() | ||
- | if HasMethod(instance, | ||
- | instance.__New() | ||
- | } | ||
- | return instance | ||
- | } | ||
- | MyClass2 := { | ||
- | Prototype: { | ||
- | | ||
- | }, | ||
- | Call: classFactory, | ||
- | someProp: 123 | ||
- | } | ||
- | myInstance2 := MyClass2() | ||
- | MsgBox " | ||
- | </ | ||
- | |||
- | === Unique behavior === | ||
- | |||
- | As mentioned previously, there are a few unique features of the '' | ||
- | |||
- | The first is // | ||
- | |||
- | The second difference is that the variable defined using '' |