jeeswg's object classes tutorial

Put simple Tips and Tricks that are not entire Tutorials in this forum
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

jeeswg's object classes tutorial

23 Aug 2018, 23:21

==================================================

JEESWG'S OBJECT CLASSES TUTORIAL

==================================================

CONTENTS

[SECTION 1: INTRODUCTION]
> QUICK SUMMARY
> INTRODUCTION: OBJECTS AND KEYS/PROPERTIES/METHODS
> INTRODUCTION: CLASSES/INSTANCES AND AUTOHOTKEY'S BUILT-IN CLASSES
> AUTOHOTKEY'S BASIC OBJECT
> AUTOHOTKEY OBJECT FUNCTIONS
> META-FUNCTIONS
> A ZOO CLASS AND BASE OBJECTS
> SUPERCLASSES/SUBCLASSES
> NESTED CLASSES
> __CLASS PROPERTY, AND BASE OBJECTS: CHASE THE BASE
> BASE OBJECTS: REPLACE THE BASE / MODIFY AN AHK ARRAY

[SECTION 2: KEYS/PROPERTIES/METHODS]
> CLASSES: VALUE PROPERTIES
> CLASSES: PROPERTIES
> CLASSES: METHODS
> CLASSES: KEYS/PROPERTIES/METHODS AND PRIORITY

[SECTION 3: META-FUNCTIONS]
> META-FUNCTIONS
> META-FUNCTIONS+: __NEW / __DELETE
> META-FUNCTIONS+: __INIT
> META-FUNCTIONS: __CALL
> META-FUNCTIONS: __GET
> META-FUNCTIONS: __SET
> __GET/__SET: MONITOR EVERY TIME A KEY IS GOTTEN/SET
> __GET/__SET: ADD KEYS AND SUBKEYS (MULTI-DIMENSIONAL ASSIGNMENTS)
> __GET/__SET: GET AND SET AT THE SAME TIME
> GENERAL CLASS EXAMPLES: DATES AND SPLIT PATH

[SECTION 4: ENUMERATORS AND MISCELLANEOUS]
> ENUMERATORS: _NEWENUM AND NEXT
> SINGLETON
> TUTORIALS / LINKS
> THE END

==================================================

[SECTION 1: INTRODUCTION]

> QUICK SUMMARY

Note: this tutorial applies to AHK v1.1, but occasional references are made to AHK v2.

Terms: object, key/method.
- Objects combine the concepts of data/variables (keys) and functions (methods).

Code: Select all

;keys:
value := MyObj["MyKey"] ;get a key's value
MyObj["MyKey"] := "NEW VALUE" ;set a key's value

;methods:
output := MyObj.MyMethod(Arg1, Arg2, Arg3) ;call a method
Terms: property.
- Properties can be key-like, with a value that is gotten/set. In AHK v2 these are called 'value properties'.
- Properties can be method-like, with a value that is generated by a getter/setter function. In AHK v2 these are called 'dynamic properties'.
- Both types of property will have the same appearance when used. (Although will appear different when defined.)

Code: Select all

value := MyObj.MyProperty ;get a property's value
MyObj.MyProperty := "NEW VALUE" ;set a property's value
- In AHK v1, keys and properties share the same namespace. The obj["key"] and obj.property syntaxes are generally interchangeable.
- In AHK v2, keys and properties are distinct. The obj["key"] and obj.property syntaxes are separate.

- In AHK v1, keys and value properties are the same thing. If a key(/value property) and dynamic property both exist, with the same name, the key takes precedence over the dynamic property.

Terms: object/instance/class.
- In simple terms:
- A class is a blueprint object.
- An instance is an object that is created based on a class blueprint.
- AutoHotkey has a Basic Object class, as well as other built-in classes (listed lower down, e.g. RegExMatch, SafeArray).

Terms: custom class.
- Users can create custom classes. I.e. their own class blueprints.
- If you create a custom class called 'MyClass', a variable is created call 'MyClass'. This can be seen by using ListVars or 'View, Variables and their contents' (on the AHK 'main window').
- (E.g. if you create a basic object instance via 'obj := {}' and define a class called 'MyClass', then IsObject(obj) and IsObject(MyClass) will both report true.)
- A custom class determines the keys/properties/methods for an instance object. It is also possible to block the addition of new keys/properties/methods.

6 methods: __Init/__New/__Get/__Set/__Call/__Delete.
- The following 6 methods are key to customisation when creating custom classes.
•__Init ['should not be used by the script', handles when an object instance is created]
•__New [handles when an object instance is created]
•__Get / __Set [handle when a non-existent key/property is gotten/set]
•__Call [handles when an existent/non-existent method is called]
•__Delete [handles when an object instance is deleted]

Terms: base.
- The base object for an object typically contains keys/properties/methods that determine how that object works.
- In AHK v1, a Basic Object has no base object.
- In AHK v1, a class object, created using the 'class' keyword, has no base object. (Unless you add one manually.)
- An instance object created via the 'new' keyboard, is an instance of a class, the instance object has the class object as its base object.
- If an attempt is made to access an object's key/property/method, but no such item is found, the object's base is checked for that item.
- ... You then check the base's base, if it exists, and the base's base's base, if it exists, etc, until you reach a base object that does not have a base object.

2 methods: _NewEnum/Next.
Terms: enumerator.
- A 'for loop' calls an object's _NewEnum method.
- The _NewEnum method returns an object called an enumerator object.
- The enumerator object will have a Next method which will return key/value pairs until there are none left.
- An enumerator object might instead return key/value pairs that are not based on an object's contents but which are computed (e.g. square numbers). It could return key/value pairs indefinitely.
- Typically a Next method outputs 2 values ByRef (a key name and a value), but it could output a number other than 2.

Further concepts.
Terms: superclass, subclass, nested class.
- Superclasses/subclasses. A superclass can have some features, a subclass inherits those features and has its own features.
- For an object based on a subclass, if both the superclass and subclass share a feature, the subclass takes priority.
- Nested class. A class that is defined within another class.

==================================================

> INTRODUCTION: OBJECTS AND KEYS/PROPERTIES/METHODS

- Two key concepts in programming are data and functions.
- Objects combine these two concepts: keys (data) and methods (functions).
- Properties work quite similarly to keys, when getting/setting values.
- However, value properties contain data (like keys), whereas dynamic properties work more like functions.
- Note in AHK v1, the key and property syntaxes are equivalent and point to the same thing: a key(/value property) or a dynamic property.

Code: Select all

;e.g. get the value of a key/property:
var := obj["key"]
var := obj.property

;e.g. set the value of a key/property:
obj["key"] := var
obj.property := var

;e.g. use a method:
obj.method(var1, var2)
==================================================

> INTRODUCTION: CLASSES/INSTANCES AND AUTOHOTKEY'S BUILT-IN CLASSES

- A class is a blueprint for a type of object, which determines the keys/properties/methods for an instance object.
- An instance object is an individual object based on a class.

- AutoHotkey provides various built-in classes (as of December 2019):
- AHK v1 only: Basic.
- Both AHK v1/v2: BoundFunc, Enumerator, Exception, File, Func, InputHook, RegExMatch, SafeArray.
- AHK v2 only, or in Array, Buffer, Class, ClipboardAll, GUI, GuiControl, Map, Menu/MenuBar.

- There are more details here:
list of every object type/property/method - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=37&t=44081

==================================================

> AUTOHOTKEY'S BASIC OBJECT

AutoHotkey combines 3 concepts into 1 built-in basic object type:
- linear arrays (e.g. obj[1] := "value") (AKA list/vector)
- associative arrays (e.g. obj["key"] := "value") (AKA dictionary/map)
- default class (custom/built-in, keys/value properties/dynamic properties/methods, can be added/modified/removed, to give custom classes)

E.g. some ways to create an AHK basic object.
Note: an AHK basic object does not have base object.

Code: Select all

;in each case obj is a basic object, and identical:
obj := ["a", "b", "c"]
obj := Array("a", "b", "c")
obj := {1:"a", 2:"b", 3:"c"}
obj := Object(1,"a", 2,"b", 3,"c")
obj := StrSplit("a,b,c", ",")

;here, MyClass is also a basic object:
class MyClass
{
	MyMethod()
	{
	}
}

MsgBox, % IsObject(ObjGetBase(obj)) ;0
MsgBox, % IsObject(ObjGetBase(MyClass)) ;0

;note: an instance of MyClass is *not* a basic object:
obj2 := new MyClass
MsgBox, % IsObject(ObjGetBase(obj2)) ;1
- Note: In AutoHotkey, a linear array is an array with 0 or more integer keys, starting at key 1.
- Note: The built-in methods for integer keys apply to positive *and* negative integers (and 0).

- When creating a custom class, the AHK basic object is used as the template.
- The built-in keys/properties/methods can be overridden/removed.
- Additional keys/properties/methods can be added.
- Note: by default there is no restriction on the keys that can be added to a custom class (it acts like an associative array). This can be changed by specifying a custom __Set method (which is discussed later on).

- Here is a list of built-in methods and properties for the basic object.

[Basic Object: methods for integer keys only (7):]
•InsertAt / RemoveAt [add/remove keys (anywhere) (and move subsequent keys)]
•Push / Pop [add/remove keys (at the end of the array)]
•MinIndex / MaxIndex / Length [get min/max key index]

[Basic Object: methods for all keys (8):]
•Delete [remove keys]
•Count [get key count]
•SetCapacity / GetCapacity [get/set an object's key count capacity, or a value's string size capacity]
•GetAddress [get the address of a value's string buffer (if the value is a string)]
•_NewEnum [returns an enumerator object used for looping through object keys/values]
•HasKey [check if an object contains a key]
•Clone [create a shallow copy of an object]

[Basic Object: properties:]
•base [the object upon which an object instance is based][if a key/property/method is not found in the instance, the base is checked]

[additional methods:]
•__Init ['should not be used by the script', handles when an object instance is created]
•__New [handles when an object instance is created]
•__Get / __Set [handle when a non-existent key/property is gotten/set]
•__Call [handles when a non-existent method is called]
•__Delete [handles when an object instance is deleted]

[additional methods (further):]
•Next [used for looping through object keys]
•ToString [returns a string, (note: the AHK v2 String function checks an object for a 'ToString' method)]

[additional properties:]
•__Class [the class name of an object instance]

- For more details see:
Basic Object - Methods & Properties | AutoHotkey
https://autohotkey.com/docs/objects/Object.htm

SOME FURTHER NOTES

- Key names can be of integer type (positive/0/negative) or string type.
- (Key names can also be objects, e.g. o := {[]:1}.)
- Key names are unique. You cannot have two keys with the same name.
- When integers beyond a certain size are used as key names, these key names are stored as strings (not integers).
- In AHK v1, key names are case insensitive. A case-sensitive alternative is to use a Scripting.Dictionary object or a custom class with a __Call method which can take actions based on the case of the key name passed to it.
- Values can be strings/integers/floats/object references/function references, and potentially other entities. (The AutoHotkey basic object is very flexible, there are no type restrictions on what the values can be.)
- When a for loop is done on an object, integer key names (and their values) are returned in numeric order, then string key names (and their values) in alphabetical order. (Thus, to treat integers as though they were strings, a workaround is to use a prefix character such as 'z' for both integers and strings.)

- Warning: Creating a key with the same name as a method/property, can block access to that method/property. E.g. if you create a key called 'HasKey', this interferes with the HasKey method.

- For more information on key names:
Objects - Definition & Usage | AutoHotkey
https://autohotkey.com/docs/Objects.htm#keys

==================================================

> AUTOHOTKEY OBJECT FUNCTIONS

- For reference, here is a list of all built-in AutoHotkey functions that relate to objects in some way.
- (The list is intended to be complete, but perhaps other/future functions could qualify.)

FUNCTIONS WITH A METHOD EQUIVALENT

- Note: AutoHotkey has built-in methods, but these can be overridden.
- E.g. the HasKey method can be overridden by defining a custom HasKey method.
- E.g. the HasKey method fails if a key(/value property) call 'HasKey' exists.
- The purpose of many (but not all) of the ObjXXX functions is to bypass any custom method behaviour, where a method has been overridden, and instead use the default behaviour.

[Basic Object: methods for integer keys only (7):]
•ObjInsertAt / ObjRemoveAt
•ObjPush / ObjPop
•ObjMinIndex / ObjMaxIndex / ObjLength

[Basic Object: methods for all keys (8):]
•ObjDelete
•ObjCount
•ObjSetCapacity / ObjGetCapacity
•ObjGetAddress
•ObjNewEnum [note: no underscore cf. '_NewEnum']
•ObjHasKey
•ObjClone

FURTHER FUNCTIONS

•Array / Object [create an array/object, equivalent to obj := [] and obj := {} respectively (note: in AHK v1 these both create a basic object) (note: AHK v2 uses []/{}/Map() to create arrays/objects/maps respectively)]
•ComObjActive / ComObjArray / ComObjConnect / ComObjCreate / ComObject / ComObjError / ComObjFlags / ComObjGet / ComObjQuery / ComObjType / ComObjValue [COM objects are not discussed in this tutorial]
•IsFunc [check if a function with a particular name exists (AHK v1 only: can check if an object is a function reference)]
•IsObject [check if a variable is an object]
•ObjAddRef / ObjRelease [modify an object's reference count (use ObjAddRef, then ObjRelease, to retrieve the count, without changing it)]
•ObjBindMethod [create a BoundFunc object for an object method (i.e. store a method, and optionally some initial parameters, as an object variable) (i.e. create a variable which acts like a function)]
•ObjGetBase / ObjSetBase [get/set an object's base object]
•ObjRawGet / ObjRawSet [get/set a key(/value property)'s value, bypass any custom class behaviour]
•String [returns a string based on a variable, will check an object for a 'ToString' method]
•StrSplit [create an array by splitting a string based on delimiters]
•Type [get a variable's type]

==================================================

> META-FUNCTIONS

- The following 3 methods are known as meta-functions:
•__Get / __Set / __Call

Objects - Definition & Usage | AutoHotkey
https://autohotkey.com/docs/Objects.htm
Meta-functions define what happens when a key is requested but not found within the target object. For example, if obj.key has not been assigned a value, it invokes the __Get meta-function. Similarly, obj.key := value invokes __Set and obj.key() invokes __Call. These meta-functions (or methods) would need to be defined in obj.base, obj.base.base or such.
- Note: __Call is invoked whether a method exists or doesn't exist.

- The following 3 methods are also sometimes referred to as meta-functions.
•__Init / __New
•__Delete

Objects - Definition & Usage | AutoHotkey
https://autohotkey.com/docs/Objects.htm
To run code when the last reference to an object is being released, implement the __Delete meta-function.
From the AHK v1.1.28.00 source code:
LPTSTR Object::sMetaFuncName[] = { _T("__Get"), _T("__Set"), _T("__Call"), _T("__Delete"), _T("__New") };
- The term 'meta-function' is not particularly important, and all 6 shall be referred to as methods throughout this document.

Here are some examples of the 5 methods (6 minus __Init) in use:
I.e. actions that trigger the methods:
__New: obj := new MyClass
__Get: var := obj.key ;__Get is called only if 'key' doesn't exist
__Set: obj["key"] := "value" ;__Set is called only if 'key' doesn't exist
__Call: obj.method() ;__Call is invoked whenever an existent/non-existent method is called (it is intended to handle non-existent methods)
__Delete: obj := ""

==================================================

> A ZOO CLASS AND BASE OBJECTS

- Here is an example custom class, with multiple keys, methods and properties. So that you can see what one looks like.
- After defining the class, we use for loops to list the contents of 4 objects (2 pairs of objects):
An instance object (an individual object created from a class blueprint), and its base object.
The class object (a class blueprint is stored as a class object), and its base object. (In AHK v1, a class object does not have a base object, unless it is added manually.)
- In AHK v1, in general, instances of a custom class, have a base object. Base objects store content that affects how an object works.
- But AHK basic objects and class objects, can be given base objects, manually.
- (Note: attempts to probe the base object of an AHK basic object, to list any keys/properties/methods, does not reveal anything. Because it does not have a base object.)

Code: Select all

class MyZooClass
{
	;in AHK v1 this is a key(/value property):
	;in AHK v2 this is a value property:
	MyValueProperty := "MyValuePropertyValue"

	;in AHK v1 this is a key(/value property):
	;in AHK v2 this is a value property:
	static MyStaticProperty := "MyStaticPropertyValue"

	MyMethod()
	{
		return "MyMethodValue"
	}

	MyDynamicProperty
	{
		get
		{
			return "MyDynamicPropertyValue"
		}
		set
		{
		}
	}

	__New()
	{
	}
	__Get()
	{
	}
	__Set()
	{
	}
	__Call()
	{
	}
	__Delete()
	{
	}
}

obj := new MyZooClass

vOutput := ""

vOutput .= "instance object:`r`n"
for vKey, vValue in obj
	vOutput .= vKey " " vValue "`r`n"
vOutput .= "`r`n"

vOutput .= "instance object base:`r`n"
for vKey, vValue in obj.base
	vOutput .= vKey " " vValue "`r`n"
vOutput .= "`r`n"

vOutput .= "class object:`r`n"
for vKey, vValue in MyZooClass
	vOutput .= vKey " " vValue "`r`n"
vOutput .= "`r`n"

vOutput .= "class object base:`r`n"
for vKey, vValue in MyZooClass.base
	vOutput .= vKey " " vValue "`r`n"
vOutput .= "`r`n"

Clipboard := vOutput
MsgBox, % vOutput

MsgBox, % IsObject(obj) " " IsObject(obj.base)
MsgBox, % IsObject(MyZooClass) " " IsObject(MyZooClass.base)

;==============================

;some additional tests to retrieve types (Type is AHK v2-only)

MsgBox(Type(MyZooClass.MyValueProperty)) ;String
MsgBox(Type(MyZooClass.MyDynamicProperty)) ;String
MsgBox(Type(MyZooClass.MyMethod)) ;Func

oVProp := ObjRawGet(MyZooClass, "MyValueProperty") ;1 1
oDProp := ObjRawGet(MyZooClass, "MyDynamicProperty") ;1 0
oMtd := ObjRawGet(MyZooClass, "MyMethod")
MsgBox(Type(oVProp)) ;String
MsgBox(Type(oDProp)) ;Property
MsgBox(Type(oMtd)) ;Func
- Here are the results of listing the contents of the 4 objects:

Code: Select all

;instance object:
;MyValueProperty MyValuePropertyValue

;instance object base:
;__Call
;__Class MyZooClass [note: created automatically, we didn't define this explicitly]
;__Delete
;__Get
;__Init [note: created automatically, we didn't define this explicitly]
;__New
;__Set
;MyDynamicProperty
;MyMethod
;MyStaticProperty MyStaticPropertyValue

;[identical to the contents of the instance object base]
;class object:
;__Call
;__Class MyZooClass [note: created automatically, we didn't define this explicitly]
;__Delete
;__Get
;__Init [note: created automatically, we didn't define this explicitly]
;__New
;__Set
;MyDynamicProperty
;MyMethod
;MyStaticProperty MyStaticPropertyValue

;class object base:
;(empty, AHK does not show any base object info for a basic object)
- What we learn from inspecting the contents of the objects is this:
- When an instance object is created, based on a class object, the instance object's base object is a clone of the class object.
- A '__Class' property is created, corresponding to the class name specified at the start of the class definition.

- Re. '__Init':
- Although we defined 'MyValueProperty' in the class object, we don't see it listed in the class object's contents. ('MyValueProperty' is created via the __Init method.)
- An '__Init' method was listed, even though we didn't specify one. The __Init method creates 'MyValueProperty'. If we remove the line MyValueProperty := "MyValuePropertyValue" from the class definition, then no __Init method will appear in the contents list.
- So, the '__Init' method is created for us by AHK, it is how the keys(/property values) for an object instance are created.

- In AHK v1, basic objects, and custom class objects have no base object. (Although they can subsequently be given a base object.)
- An instance object of a custom class, has a class object as its base object.
- Be careful when editing a base object, it will affect existing and future instance objects that share that base object.

- One minor point: the availability of the 'base' keyword in class definitions:

Code: Select all

obj := new MyClass

obj.prop := 1
MyClass.base := {prop:2}
MyClass.prop := 3
obj.MyMethod()

obj.base.prop := 4
obj.MyMethod()

class MyClass
{
	MyMethod()
	{
		MsgBox, % this.prop ;1
		MsgBox, % base.prop ;2
		MsgBox, % MyClass.base.prop ;2
		MsgBox, % this.base.prop ;3 then 4
		MsgBox, % MyClass.prop ;3 then 4
	}
}
- Note: I have not yet found a definitive answer re. whether AHK basic objects have a base object or not.
- E.g. this suggests that AHK basic objects do not have a base object:

Code: Select all

;AHK v1
obj := []
MsgBox, % IsObject(obj.base) ;0 ;reports that the AHK basic object has no base object
- But perhaps one can say that, conceptually, AHK basic objects do have a default base object. If an instance of a class, does not have its own MyMethod method, it checks for a MyMethod in its base. Similarly, if an AHK basic object does not have its own custom HasKey method, it falls back to the default HasKey method.
- Note: the AHK documentation talks about a 'default base object' for non-objects. E.g. it defines what happen when you do "mystring".method() or myvar.method() when var does not contain an object.

- This chapter has hinted at virtually everything that we will look at in the following chapters.
- 3 other things that will be mentioned are: superclasses/subclasses (parent/child classes, extending classes), nested classes (classes within classes), and enumerator objects.
- Superclasses/subclasses and nested classes will be introduced in the next two chapters.
- After that we will look more closely at specific aspects of custom classes.
- At the very end, we will consider enumerator objects. This is because issues relating to enumerators are somewhat separate from those applying to classes generally.

==================================================

> SUPERCLASSES/SUBCLASSES

- When an instance of a superclass is created, any subclasses are irrelevant.
- When an instance of a subclass is created, if a key/property/method does not exist in the subclass, the superclass is checked.
- E.g. in an instance of a subclass, if the superclass and subclass each have a method with the same name, the subclass method takes precedence.

Code: Select all

class MySuperClass
{
	Method1()
	{
		return A_ThisFunc
	}
	Method2()
	{
		return A_ThisFunc
	}
}

class MySubClass extends MySuperClass
{
	Method2()
	{
		return A_ThisFunc
	}
	Method3()
	{
		return A_ThisFunc
	}
}

oArray1 := new MySuperClass
oArray2 := new MySubClass
vOutput := oArray1.__Class ;MySuperClass
. "`r`n" oArray1.base.__Class ;MySuperClass
. "`r`n" oArray1.base.base.__Class ;(blank)
. "`r`n" oArray1.Method1() ;MySuperClass.Method1
. "`r`n" oArray1.Method2() ;MySuperClass.Method2
. "`r`n" oArray1.Method3() ;(blank)
. "`r`n" "==============="
. "`r`n" oArray2.__Class ;MySubClass
. "`r`n" oArray2.base.__Class ;MySubClass
. "`r`n" oArray2.base.base.__Class ;MySuperClass
. "`r`n" oArray2.Method1() ;MySuperClass.Method1
. "`r`n" oArray2.Method2() ;MySubClass.Method2
. "`r`n" oArray2.Method3() ;MySubClass.Method3
Clipboard := vOutput
MsgBox, % vOutput

;oArray2 can access both versions of 'Method2'
MsgBox, % oArray2.Method2() ;MySubClass.Method2
MsgBox, % oArray2.base.base.Method2() ;MySuperClass.Method2

;==============================

;output:
;MySuperClass
;MySuperClass
;
;MySuperClass.Method1
;MySuperClass.Method2
;
;===============
;MySubClass
;MySubClass
;MySuperClass [base of base is superclass]
;MySuperClass.Method1
;MySubClass.Method2 [subclass Method2 takes precedence over superclass Method2]
;MySubClass.Method3
- When a subclass extends a superclass, the superclass is the base object of the subclass.
- E.g. here C.base is B, C.base.base is A.

Code: Select all

obj := new C
MsgBox, % obj.__Class ;C ;(this points to obj.base.__Class)
MsgBox, % obj.base.__Class ;C
MsgBox, % obj.base.base.__Class ;B
MsgBox, % obj.base.base.base.__Class ;A

MsgBox, % A.base.__Class ;(blank)
MsgBox, % B.base.__Class ;A
MsgBox, % C.base.__Class ;B

class A
{
}
class B extends A
{
}
class C extends B
{
}
==================================================

> NESTED CLASSES

- A nested class is accessible from the class that contains it, but is not listed amongst its contents.

Code: Select all

class MyOuterClass
{
	class MyInnerClass
	{
	}
}
obj1 := new MyOuterClass
obj2 := new MyOuterClass.MyInnerClass
;obj3 := new MyInnerClass ;causes error in AHK v2

;MyInnerClass is not listed in obj1's contents
vOutput := ""
for vKey, vValue in obj1
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput ;(blank)

vOutput := ""
for vKey, vValue in obj2
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput ;(blank)

;however, obj1.MyInnerClass is listed as an object
MsgBox, % IsObject(obj1.MyInnerClass)
- This post has a neat comparison of subclassing v. nesting:
Question About Classes - Ask for Help - AutoHotkey Community
https://autohotkey.com/board/topic/85528-question-about-classes/#entry545206

==================================================

> __CLASS PROPERTY, AND BASE OBJECTS: CHASE THE BASE

- If we define an empty class, it will produce an object identical to an AHK basic object, with one difference, the class name.
- Any instance object created from a class object will have a property called '__Class', in its base object. The base object, of the instance object, is the class object. The value of '__Class' is the name of the class.
- In the example below, we will demonstrate modifying the value of a 'base' property directly, and assigning an object for the base via 'obj.base := ' and ObjSetBase.
- We will also demonstrate listing the key/value pairs of an object, via a for loop.

Code: Select all

class MyEmptyClass
{
}

MsgBox, % MyEmptyClass.__Class ;MyEmptyClass

obj1 := {} ;basic AHK object
obj2 := new MyEmptyClass
obj3 := new MyEmptyClass() ;equivalent to the line above

MsgBox, % obj1.__Class ;(blank)
MsgBox, % obj2.__Class ;MyEmptyClass
MsgBox, % obj3.__Class ;MyEmptyClass

MsgBox, % obj1.base.__Class ;(blank)
MsgBox, % obj2.base.__Class ;MyEmptyClass
MsgBox, % obj3.base.__Class ;MyEmptyClass

;obj1 does not contain a __Class property:
vOutput := ""
for vKey, vValue in obj1
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput ;(blank)

;obj1's base does not contain a __Class property:
vOutput := ""
for vKey, vValue in obj1.base
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput ;(blank)

;obj2 does not contain a __Class property:
vOutput := ""
for vKey, vValue in obj2
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput ;(blank)

;obj2's base *does* contain a __Class property:
vOutput := ""
for vKey, vValue in obj2.base
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput ;__Class MyEmptyClass

MsgBox, % IsObject(obj1.base) ;0 ;reports that the AHK basic object has no base object
MsgBox, % IsObject(obj2.base) ;1

;change the objects' class names:
obj1.base.__Class := "NewClassName1" ;doesn't work
obj2.base.__Class := "NewClassName2"
MsgBox, % obj1.__Class ;(blank)
MsgBox, % obj2.__Class ;NewClassName2

obj1.base := {__Class:"NewClassName3"}
obj2.base := {__Class:"NewClassName4"}
MsgBox, % obj1.__Class ;NewClassName3
MsgBox, % obj2.__Class ;NewClassName4

ObjSetBase(obj1, {__Class:"NewClassName5"})
ObjSetBase(obj2, {__Class:"NewClassName6"})
MsgBox, % obj1.__Class ;NewClassName5
MsgBox, % obj2.__Class ;NewClassName6
- From the tests above we saw that obj2.base has a property called '__Class', but obj2 doesn't have such a property.
- 'obj2.base.__Class' returns a value as expected.
- obj2 doesn't have a property called '__Class', so it checks it's base, so 'obj2.__Class' returns the value of 'obj2.base.__Class'.
- In general, for objects, if obj.prop doesn't exist, a check is done for obj.base.prop and obj.base.base.prop etc. (You could call this to 'chase the base'.)
- If such a property is found, the value of the property is returned.
- If no property is found, a blank string is returned.

- Note: When a class is defined, a class object (a variable) with the same name as the class is created that contains the object. Viewable via ListVars or 'View, Variables and their contents'.
- So, instance objects, and class objects, are both types of object, stored in a variable.

Code: Select all

;this class definition results in a variable being created called 'MyClass'
class MyClass
{
}

;create an instance object
obj := new MyClass

;refer to an instance object
MsgBox, % IsObject(obj) ;1

;refer to the class object
MsgBox, % IsObject(MyClass) ;1

;get the class name
MsgBox, % obj.__Class ;MyClass
MsgBox, % MyClass.__Class ;MyClass
MsgBox, % obj.base.__Class ;MyClass
MsgBox, % MyClass.base.__Class ;(blank)
- Note: we learnt in the 'A ZOO CLASS AND BASE OBJECTS' chapter, that class objects do not have a base object, and are AHK basic objects.

- Some tests comparing an AHK basic object, a custom class object, and an instance of a custom class object:
- (Note: in AHK v1, HasKey checks for the existence of a key(/value property).)

Code: Select all

class MyClass
{
}

;a custom class object:
MsgBox, % MyClass.HasKey("base") ;0
MsgBox, % IsObject(MyClass.base) ;0
MsgBox, % MyClass.base.__Class ;(blank)

;an instance of a custom class object:
obj := new MyClass
MsgBox, % obj.HasKey("base") ;0
MsgBox, % IsObject(obj.base) ;1
MsgBox, % obj.base.__Class ;MyClass

;for comparison, an AHK basic object:
obj := []
MsgBox, % obj.HasKey("base") ;0
MsgBox, % IsObject(obj.base) ;0
MsgBox, % obj.base.__Class ;(blank)
==================================================

> BASE OBJECTS: REPLACE THE BASE / MODIFY AN AHK ARRAY

- Here we try setting the base object via obj.base, ObjSetBase and ObjRawSet:

Code: Select all

class MyClass
{
}

vOutput := ""

obj := new MyClass
vOutput .= obj.HasKey("base") " " obj.__Class " " obj.base.__Class "`r`n"

obj := new MyClass
obj.base := {__Class:"MyClassNew"}
vOutput .= obj.HasKey("base") " " obj.__Class " " obj.base.__Class "`r`n"

obj := new MyClass
ObjSetBase(obj, {__Class:"MyClassNew"})
vOutput .= obj.HasKey("base") " " obj.__Class " " obj.base.__Class "`r`n"

obj := new MyClass
ObjRawSet(obj, "base", {__Class:"MyClassNew"})
vOutput .= obj.HasKey("base") " " obj.__Class " " obj.base.__Class "`r`n"

Clipboard := vOutput
MsgBox, % vOutput

;results:
;0 MyClass MyClass ;original
;0 MyClassNew MyClassNew ;obj.base applied
;0 MyClassNew MyClassNew ;ObjSetBase applied
;1 MyClass MyClassNew ;ObjRawSet applied
- What the examples show is this:
- If you create a key(/value property) called 'base', this overrides the 'base' property.
- So 'obj.base.__Class' gets the contents from the property called 'base'.
- However, when 'obj.__Class' fails to find a property called '__Class', it checks the base object, it does not check the key(/value property) called 'base'. So in this respect, creating the key 'base' has not overridden the property 'base'.

- Some examples of defining/modifying a base object, to change how getting/setting values is done:

Code: Select all

;modify an AHK array:

;edit the base object:
oArray.base := []
oArray.base.__Class := "hello"
vOutput := ""
for vKey, vValue in oArray.base
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput

;==============================

;add a __Get property, and thus change the default return value:
oArray := []
MsgBox, % oArray[1] ;(blank)
oArray.base := {"__Get":"MyFuncReturnHello"}
MsgBox, % oArray[1] ;hello

MyFuncReturnHello()
{
	return "hello"
}

;==============================

;temporarily add a __Set method, and thus prevent new keys being created:
oArray := []
oArray[1] := "a"
oArray.base := {"__Set":"MyFuncPreventObjSet"}
oArray[2] := "b"

oArray.base.Delete("__Set") ;restore the ability to set a key's value

;oArray.Delete("base") ;doesn't work because 'base' is not a key(/value property)

;note: these attempted fixes (to restore setting) did not work,
;because we are tempting to set the value of a property, 'base',
;but MyFuncPreventObjSet is preventing this
;oArray.base := {"__Set":""}
;oArray.base := {}
;oArray.base := ""

oArray[3] := "c"
MsgBox, % oArray[1] "_" oArray[2] "_" oArray[3]

MyFuncPreventObjSet()
{
	MsgBox, % A_ThisFunc
	return
}

;==============================
- Note: it has not been fully explained why the attempted fixes did not work.

- Some points re. changing the base:
Classes in AHK, a Dissection (Advanced) - Page 3 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=6177&p=55441#p55441
Classes in AHK, a Dissection (Advanced) - Page 3 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=6177&p=55477#p55477

==================================================

[SECTION 2: KEYS/PROPERTIES/METHODS]

> CLASSES: VALUE PROPERTIES

- Here are some classic things to do with value properties and classes, which will be covered below.
- Note: in AHK v1, keys and value properties are the same thing.
- Create an object pre-populated with some value properties.
- ... Change those properties for future instances but leave existing instances unchanged.
- ... Change those properties for future instances *and* existing instances.
- Log each time an object is created for a particular class.

PRE-POPULATE AN OBJECT WITH VALUE PROPERTIES

- An example of defining value properties within the class body. With/without using 'static'.
- When 'static' is used, the property is placed into the base object (into the class object).
- When 'static' is omitted, a local copy of the property is placed into the instance object.

Code: Select all

class MyKeyClass
{
	static prop1 := "value1" ;one copy only, in the class object
	prop2 := "value2" ;one local copy for all instance objects
}

obj := new MyKeyClass
MsgBox, % obj.prop1 ;value1 ;value retrieved from base object
MsgBox, % obj.prop2 ;value2
MsgBox, % obj.base.prop1 ;value1
MsgBox, % obj.base.prop2 ;(blank)
obj.Delete("prop1") ;does nothing, 'prop1' exists in the base object, but not the object proper
obj.Delete("prop2") ;prop2 is deleted
MsgBox, % obj.prop1 ;value1
MsgBox, % obj.prop2 ;(blank)

obj.base.Delete("prop1") ;prop1 is deleted
MsgBox, % obj.base.prop1 ;(blank)
vOutput := ""
for vKey, vValue in obj.base
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput

;this shows that MyKeyClass.prop1 was also deleted:
vOutput := ""
for vKey, vValue in MyKeyClass
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput
- An attempt is made to delete 'prop1', only the object proper is checked.
- An attempt is made to retrieve the contents of 'prop1', the object proper is checked, there is no such property, so the base object is then checked.

LOG EACH TIME AN OBJECT IS CREATED

- Here is an example of a class that keeps an instance count.
- And gives each object an issue number.
- It uses the __New method, discussed in a later chapter.

Code: Select all

class MyIncrementClass
{
	static issued := 0
	__New()
	{
		MyIncrementClass.issued++
		this.num := MyIncrementClass.issued
	}
}

;create new instances each with an issue number
obj1 := new MyIncrementClass
obj2 := new MyIncrementClass
obj3 := new MyIncrementClass
MsgBox, % obj1.num ;1
MsgBox, % obj2.num ;2
MsgBox, % obj3.num ;3

;get total number of classes issued
MsgBox, % MyIncrementClass.issued ;3
AN OBJECT WITH PROPERTIES (CHANGE THE PROPERTY VALUES FOR EXISTING AND FUTURE INSTANCES)

- In this example, the property content is changed for all instances.

Code: Select all

class MyPropClass
{
	static prop := "MyValue"
}
obj1 := new MyPropClass
obj2 := new MyPropClass
MsgBox, % obj1.prop ;MyValue
MsgBox, % obj2.prop ;MyValue

MyPropClass.prop := "MyValueNEW"
MsgBox, % obj1.prop ;MyValueNEW
MsgBox, % obj2.prop ;MyValueNEW

MsgBox

;note: if we assign to obj1.prop this will create obj1.prop (and will not override MyPropClass.prop aka obj.base.prop)
obj1.prop := "hello"
MsgBox, % obj1.prop ;hello
MsgBox, % obj1.base.prop ;MyValueNEW
MsgBox, % obj2.prop ;MyValueNEW ;reads obj2.base.prop
MsgBox, % MyPropClass.prop ;"MyValueNEW"
- We could use a dynamic property to refer to a global variable. Changing the value of the global variable would give us a way to affect future and existing instances.

Code: Select all

global vGblMyValue := "MyValue"
class MyKeyClass
{
	MyProperty
	{
		get
		{
			global vGblMyValue
			return vGblMyValue
		}
		set
		{
		}
	}
}

obj1 := new MyKeyClass
MsgBox, % obj1.MyProperty ;MyValue

vGblMyValue := "MyValueNEW"
MsgBox, % obj1.MyProperty ;MyValueNEW

obj2 := new MyKeyClass
MsgBox, % obj2.MyProperty ;MyValueNEW
- Instead of using a static variable, we could keep a list of all object instances as they are created, and update the values for each key.

Code: Select all

class MyKeepTrackClass
{
	static instances := []
	__New()
	{
		this.prop := "MyValue"
		this.instances.Push(&this)
	}
	UpdateAllProps(vValue)
	{
		this.prop := vValue
		for _, vAddr in this.instances
		{
			obj := Object(vAddr)
			if IsObject(obj)
				obj.prop := vValue
		}
	}
}

;create new instances each with an issue number
obj1 := new MyKeepTrackClass
obj2 := new MyKeepTrackClass
MsgBox, % obj1.prop ;MyValue
MsgBox, % obj2.prop ;MyValue

MyKeepTrackClass.UpdateAllProps("MyValueNEW")
;obj1.UpdateAllProps("MyValueNEW") ;also works
MsgBox, % obj1.prop ;MyValueNEW
MsgBox, % obj2.prop ;MyValueNEW
- When multiple instances are derived from a class. Modifying the base of one instance affects all instances.
- Here is a way to preserve the features of an instance, but allowing any edits to its base, to *not* affect other instances.

Code: Select all

class MyPropClass
{
	static prop := "MyValue"
}
obj1 := new MyPropClass
obj2 := new MyPropClass
MsgBox, % obj1.prop ;MyValue
MsgBox, % obj2.prop ;MyValue

MyPropClass.prop := "MyValueNEW"
MsgBox, % obj1.prop ;MyValueNEW
MsgBox, % obj2.prop ;MyValueNEW

base2 := ObjGetBase(obj2)
ObjSetBase(obj2, ObjClone(base2))

MyPropClass.prop := "MyValueNEWER"
MsgBox, % obj1.prop ;MyValueNEWER"
MsgBox, % obj2.prop ;MyValueNEW" ;this time the value is unaffected
PRE-POPULATE AN OBJECT WITH KEYS (CHANGE FUTURE INSTANCES BUT NOT EXISTING INSTANCES)

- We can change a key's value for future instances like so:
- Each time an instance is created, it creates a key which is a snapshot of the class's key.

Code: Select all

class MyPropClass
{
	static prop := "MyValue"
	__New()
	{
		this.prop := this.base.prop
	}
}
obj1 := new MyPropClass

MyPropClass.prop := "MyValueNEW"
obj2 := new MyPropClass

MyPropClass.prop := "MyValueNEWER"
obj3 := new MyPropClass

MsgBox, % obj1.prop ;MyValue
MsgBox, % obj2.prop ;MyValueNEW
MsgBox, % obj3.prop ;MyValueNEWER
THE USE OF 'STATIC' WITHIN CLASSES VERSUS ELSEWHERE

- When 'static' is used within a class body (a class definition), it has a special meaning. It means that a key(/value property) will be created inside the class object, and in the base of each instance of that class.
- That 'static' key(/value property) will be accessible via 'myclass.prop' or 'myinstance.base.prop', its value can be changed at a later date.
- ... This is in contrast to static values inside functions or dynamic property getters/setters, where you can't access static values directly.
- Note: a method, a property.get, and a property.set, these are all functions and their function names can be displayed by using A_ThisFunc.

==================================================

> CLASSES: PROPERTIES

- Here is a simple class with a dynamic property that works almost identically to a key(/value property).
- There is one difference, a key(/value property) is created which stores the value used by the property.
- Note: the special variable 'this' is available in property getters/setters. Also, in property setters, the special variable 'value' is available.

Code: Select all

class MyPropertyClass
{
	MyProperty
	{
		get
		{
			return this._MyProperty
		}
		set
		{
			this._MyProperty := value
			return value
		}
	}
}

obj := new MyPropertyClass
MsgBox, % obj["MyKey"] := "value"
MsgBox, % obj["MyKey"]
MsgBox, % obj.MyProperty := "value"
MsgBox, % obj.MyProperty
- Dynamic properties are similar in function to keys, but work like methods behind the scenes.
- They allow properties with values that are generated dynamically, and allow for read-only properties.
- A read-only property should define what happens when an attempt it made to set its value, otherwise it would be possible for a key(/value property) to be created, which had the same name as the property, and that key would take precedence over the property. The key's value would be returned, not the dynamic property's.
- Note: it's quite common in programming for keys/properties/methods that are intended for internal use only, to have names that begin with an underscore, '_'.
- Note: when dynamic properties are defined in a class, AHK defines them as functions with names of the form 'class.property.get' and 'class.property.set' (which can be seen by using A_ThisFunc).

Code: Select all

class MyPropertyClass
{
	;MyProperty works much like a normal key
	MyProperty
	;MyProperty[] ;equivalent to line above (square brackets are optional)
	{
		get
		{
			;MsgBox, % A_ThisFunc ;MyPropertyClass.MyProperty.get
			;a special variable called 'this' is used, which represents an object instance:
			return this._MyProperty
		}
		set
		{
			;MsgBox, % A_ThisFunc ;MyPropertyClass.MyProperty.set
			;a special variable called value is used,
			;e.g. for: 'obj.MyProperty := "hello"', value is equal to 'hello'
			this._MyProperty := value
		}
	}

	MyReadOnlyProperty
	{
		get
		{
			return "ReadOnlyValue"
		}
		set ;this is needed to make it read-only, otherwise you could create a key called 'MyReadOnlyProperty'
		{
		}
	}

	;a write-only property would be highly unusual
	MyWriteOnlyProperty
	{
		get
		{
		}
		set
		{
			_MyWriteOnlyProperty := value
		}
	}

	Now
	{
		get
		{
			return A_Now
		}
		set
		{
		}
	}
}

obj := new MyPropertyClass

;MyProperty works outwardly much like a key:
obj.MyProperty := "MyPropertyValue"
MsgBox, % obj.MyProperty ;MyPropertyValue
obj["MyKey"] := "MyKeyValue"
MsgBox, % obj["MyKey"] ;MyKeyValue

;however, MyProperty also uses a key(/value property) called '_MyProperty' to store its value:
MsgBox, % obj._MyProperty ;MyPropertyValue

;this assignment won't work, because the property is read-only:
obj.MyReadOnlyProperty := "new value"
MsgBox, % obj.MyReadOnlyProperty ;ReadOnlyValue

;get the current date and time:
MsgBox, % obj.Now
Sleep, 1000
MsgBox, % obj.Now

;obj contains various keys(/value properties):
vOutput := ""
for vKey, vValue in obj
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput
;output:
;_MyProperty MyPropertyValue
;MyKey MyKeyValue

;obj's base contains various properties:
vOutput := ""
for vKey, vValue in ObjGetBase(obj)
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput
;output:
;__Class MyPropertyClass
;MyProperty
;MyReadOnlyProperty
;MyWriteOnlyProperty
;Now
- Here is an example for getting information about properties:

Code: Select all

;AHK v2
;using AHK v2 to use the Type function
class MyPropertyClass
{
	MyProperty
	{
		get
		{
			;MsgBox, % A_ThisFunc
			return "MyPropertyValue"
		}
		set
		{
			;MsgBox, % A_ThisFunc
		}
	}
}

;copy a property from a class to an array:

;some tests:
MsgBox(IsFunc("MyPropertyClass.MyProperty")) ;0
MsgBox(IsObject(MyPropertyClass.MyProperty)) ;0
MsgBox(Type(MyPropertyClass.MyProperty)) ;String ;note: the return value is a string
oProp := ObjRawGet(MyPropertyClass, "MyProperty")

MsgBox(IsObject(oProp)) ;1
MsgBox(Type(oProp)) ;Property

MsgBox(IsFunc("MyPropertyClass.MyProperty.get")) ;2
;MsgBox(IsObject(MyPropertyClass.MyProperty.get)) ;Error:  No object to invoke.
;MsgBox(Type(MyPropertyClass.MyProperty.get)) ;Property

;assign a property to an object:
obj := {}
MsgBox(obj.MyProperty) ;(blank) ;as expected
oProp := ObjRawGet(MyPropertyClass, "MyProperty")
ObjRawSet(obj, "MyProperty", oProp)
MsgBox(obj.MyProperty) ;MyPropertyValue

;assign a property to an object:
obj := {}
MsgBox(obj.MyProperty) ;(blank) ;as expected
for vKey, vValue in MyPropertyClass
{
	if (vKey = "MyProperty")
		ObjRawSet(obj, "MyProperty", vValue)
}
MsgBox(obj.MyProperty) ;MyPropertyValue
==================================================

> CLASSES: METHODS

- Here is a simple class with some methods.
- Methods are essentially functions stored within a class.
- Note: in AutoHotkey, methods are functions with names of the form 'class.method'.
- Note: IsObject and Type demonstrate that methods are objects with type 'Func'.

Code: Select all

class MyMethodClass
{
	MyMethod()
	{
		;MsgBox, % A_ThisFunc ;MyMethodClass.MyMethod
		return "MyMethod"
	}
	Add(num1, num2)
	{
		return num1 + num2
	}
	Concatenate(text1, text2)
	{
		return text1 text2
	}
}

obj := new MyMethodClass
MsgBox, % IsFunc("MyMethodClass.MyMethod") ;2 (a func with 1 parameter)
MsgBox, % IsObject(obj.MyMethod) ;1
;MsgBox(Type(obj.MyMethod)) ;Func ;AHK v2

MsgBox, % obj.MyMethod() ;MyMethod
MsgBox, % obj.Add(1, 2) ;3
MsgBox, % obj.Concatenate("abc", "def") ;abcdef

;AHK v1 only: calling methods with a dynamic method name (computed method name)
var := "MyMethod"
MsgBox, % obj[var]() ;MyMethod
MsgBox, % obj["Add"](1, 2) ;3
MsgBox, % obj["Concatenate"]("abc", "def") ;abcdef
- Functions v. methods.
- In a function, you specify the object explicitly (as the first parameter).
- In a method, you don't specify the object explicitly, it is an implicit first parameter, accessible by using 'this'.
- This is an example of 'this':

Code: Select all

;an explicit parameter (obj):
MyFunc(obj)
{
	MsgBox, % obj.Length()
	MsgBox, % &obj
}

class MyThisClass
{
	;an implicit 'this' parameter:
	MyMethod()
	{
		MsgBox, % this.Length()
		MsgBox, % &this
	}
}

oArray := new MyThisClass
oArray.Push("a", "b", "c")
MsgBox, % oArray.Length() ;3
MsgBox, % &oArray ;(address)
oArray.MyMethod() ;will give the same values: 3 and (address)

oArray := ["a", "b", "c"]
oArray.base := {MyMethod:Func("MyFunc")}
MsgBox, % oArray.Length() ;3
MsgBox, % &oArray ;(address)
MyFunc(oArray) ;will give the same values: 3 and (address)
- Here is an example of taking a function, and adding it as a method to an object.

Code: Select all


;==============================

class MyMethodClass
{
	MyMethod()
	{
		MsgBox, % this.MyProp
	}
	MyMethod2(var1, var2, var3)
	{
		MsgBox, % this.MyProp " " var1 " " var2 " " var3
	}
}

MyFunc(obj)
{
	MsgBox, % obj.MyProp
}

MyFunc2(obj, var1, var2, var3)
{
	MsgBox, % obj.MyProp " " var1 " " var2 " " var3
}

;==============================

myobj := {}
myobj.MyProp := "value"

;the function expects 1 parameter, but we pass 0 parameters,
;the object is passed implicitly as the 1st parameter
myobj.MyMethod := Func("MyFunc")
myobj.MyMethod() ;value
myobj.MyMethod.Call(myobj) ;value ;equivalent to line above (but with the object passed explicitly)

;the function expects 4 parameters, but we pass 3 parameters,
;the object is passed implicitly as the 1st parameter
myobj.MyMethod2 := Func("MyFunc2")
myobj.MyMethod2(1, 2, 3) ;value 1 2 3
myobj.MyMethod2.Call(myobj, 1, 2, 3) ;value 1 2 3 ;equivalent to line above (but with the object passed explicitly)

;myobj.MyMethod2(1, 2) ;too few parameters, this silently fails in AHK v1 (but gives 'Error:  Missing a required parameter.' in AHK v2)
;myobj.MyMethod2.Call(myobj, 1, 2) ;too few parameters, this silently fails in AHK v1 (but gives 'Error:  Missing a required parameter.' in AHK v2)

;==============================

;using the class:
myobj := new MyMethodClass
myobj.MyProp := "value"

myobj.MyMethod() ;value
myobj.MyMethod.Call(myobj) ;value

myobj.MyMethod2(1, 2, 3) ;value 1 2 3
myobj.MyMethod2.Call(myobj, 1, 2, 3) ;value 1 2 3
- Some further examples of dynamic method calls:

Code: Select all

oArray := ["a", "b", "c"]
vMethod := "Length"
MsgBox, % oArray.Length() ;3
;MsgBox, % oArray.%vMethod%() ;AHK v1: Error: Ambiguous or invalid use of "."
MsgBox, % oArray[vMethod]() ;3
;MsgBox, % oArray[vMethod] ;(blank)
;MsgBox, % ObjBindMethod(oArray, vMethod).() ;3 ;deprecated syntax
MsgBox, % ObjBindMethod(oArray, vMethod).Call() ;3
oFunc := ObjBindMethod(oArray, vMethod)
;MsgBox, % oFunc.() ;3 ;deprecated syntax
MsgBox, % oFunc.Call() ;3
MsgBox, % %oFunc%() ;3
==================================================

> CLASSES: KEYS/PROPERTIES/METHODS AND PRIORITY

- You cannot have a class key(/value property), or instance key(/value property), or dynamic property, or method, with the same name in a class definition.

Code: Select all

;all 4 of these items cause a 'Duplicate declaration' error, if any 2 (or more) are uncommented
class MySameNameClass
{
	;class key(/value property):
	static item := 1

	;instance key(/value property):
	item := 2

	;dynamic property:
	/*
	item
	{
		get
		{
			return 3
		}
		set
		{
		}
	}
	*/

	;method:
	/*
	item()
	{
		return 4
	}
	*/
}
- You cannot have a class with a method and a dynamic property that share the same name. You get a 'Duplicate declaration' error.
- If a key(/value property) and a method share the same name, the key takes priority.
- If a key(/value property) and a dynamic property both share the same name, the key takes priority.
- This example demonstrates some same name issues:

Code: Select all

class MySameNameClass
{
	MyKeyOrProperty
	{
		get
		{
			return "property"
		}
		set
		{
		}
	}
	MyKeyOrMethod()
	{
		return "method"
	}
}

obj := new MySameNameClass

;test what happens when 'keys with the same name as the property/method' don't exist:
MsgBox, % "TEST 0: default behaviour"
MsgBox, % obj.MyKeyOrProperty ;property
MsgBox, % obj.MyKeyOrProperty() ;property
MsgBox, % obj.MyKeyOrMethod ;(blank)
MsgBox, % obj.MyKeyOrMethod() ;method

;create keys with the same names,
;test what happens when keys with the same name do exist:
ObjRawSet(obj, "MyKeyOrProperty", "key")
ObjRawSet(obj, "MyKeyOrMethod", "key")
MsgBox, % "TEST 1A"
MsgBox, % obj.MyKeyOrProperty ;key
MsgBox, % obj.MyKeyOrProperty() ;(blank)
MsgBox, % obj.MyKeyOrMethod ;key
MsgBox, % obj.MyKeyOrMethod() ;(blank)

;delete the keys, and the original behaviour is restored:
obj.Delete("MyKeyOrProperty")
obj.Delete("MyKeyOrMethod")
MsgBox, % "TEST 1B"
MsgBox, % obj.MyKeyOrProperty ;property
MsgBox, % obj.MyKeyOrProperty() ;property
MsgBox, % obj.MyKeyOrMethod ;(blank)
MsgBox, % obj.MyKeyOrMethod() ;method

;creating a key(/value property) called 'base' does not block the methods/properties:
ObjRawSet(obj, "base", "key")
MsgBox, % "TEST 2A"
MsgBox, % obj.MyKeyOrProperty ;property
MsgBox, % obj.MyKeyOrProperty() ;property
MsgBox, % obj.MyKeyOrMethod ;(blank)
MsgBox, % obj.MyKeyOrMethod() ;method

;delete the key, and the original behaviour is restored:
obj.Delete("base")
MsgBox, % "TEST 2B"
MsgBox, % obj.MyKeyOrProperty ;property
MsgBox, % obj.MyKeyOrProperty() ;property
MsgBox, % obj.MyKeyOrMethod ;(blank)
MsgBox, % obj.MyKeyOrMethod() ;method

;creating a key(/value property) called 'base', also blocks the methods/properties:
obj.base := "key"
MsgBox, % "TEST 3A"
MsgBox, % obj.MyKeyOrProperty ;(blank)
MsgBox, % obj.MyKeyOrProperty() ;(blank)
MsgBox, % obj.MyKeyOrMethod ;(blank)
MsgBox, % obj.MyKeyOrMethod() ;(blank)

;attempt to delete the key(/value property), but the original behaviour is not restored:
;this would delete a key called 'base', but not the property 'base'
obj.Delete("base")
MsgBox, % "TEST 3B"
MsgBox, % obj.MyKeyOrProperty ;(blank)
MsgBox, % obj.MyKeyOrProperty() ;(blank)
MsgBox, % obj.MyKeyOrMethod ;(blank)
MsgBox, % obj.MyKeyOrMethod() ;(blank)

;setting the base to the class object restores the original behaviour:
obj.base := MySameNameClass
MsgBox, % "TEST 3C"
MsgBox, % obj.MyKeyOrProperty ;property
MsgBox, % obj.MyKeyOrProperty() ;property
MsgBox, % obj.MyKeyOrMethod ;(blank)
MsgBox, % obj.MyKeyOrMethod() ;method
- To create a key that overwrites a method, just assign a key, e.g. obj["key"] := value, or use ObjRawSet.
- To create a key that overwrites a dynamic property, assigning a key(/value property) won't work, e.g. obj["key"] := value, that will just assign a value to the existing property, so use ObjRawSet.

Code: Select all

class MyClass
{
	MyProperty
	{
		get
		{
			return "property"
		}
		set
		{
		}
	}
	MyMethod()
	{
		return "method"
	}
}

obj := new MyClass

;==============================

MsgBox, % obj.MyProperty ;property

;this fails to add a key(/value property),
;since MyProperty is a dynamic property,
;this tries to alter the value of MyProperty
;(but MyProperty is read-only):
obj["MyProperty"] := "new value"
MsgBox, % obj.MyProperty ;property

;ObjRawSet, however, does succeed in adding a key, the key's value is retrieved, not the dynamic property's
ObjRawSet(obj, "MyProperty", "new value")
MsgBox, % obj.MyProperty ;new value

;the key is removed, calling the method works again:
obj.Delete("MyProperty")
MsgBox, % obj.MyProperty ;property

;==============================

MsgBox, % obj.MyMethod() ;method

;this adds a key, calling the method now fails:
obj["MyMethod"] := "new value"
MsgBox, % obj.MyMethod() ;(blank)

;the key is removed, calling the method works again:
obj.Delete("MyMethod")
MsgBox, % obj.MyMethod() ;method

;alternatively, this adds a key, calling the method now fails:
ObjRawSet(obj, "MyMethod", "new value")
MsgBox, % obj.MyMethod() ;(blank)

;again, the key is removed, calling the method works again:
obj.Delete("MyMethod")
MsgBox, % obj.MyMethod() ;method
- Here is an example of 'replacing a method with itself'.
- Adding a key with the same name as a method, can prevent the method from working.
- However, if that added key is a func object, which acts like the method, then, even though you've added a key, calling the method works like before.

Code: Select all

obj := ["a", "b", "c"]
MsgBox, % obj.Length() ;3
MsgBox, % obj.Length ;(blank)

;if we create a key(/value property) called 'Length', the method fails:
obj.Length := "value"
MsgBox, % obj.Length() ;(blank)
MsgBox, % obj.Length ;value

;if we delete the key(/value property) called 'Length', the method works again:
obj.Delete("Length")
MsgBox, % obj.Length() ;3
MsgBox, % obj.Length ;(blank)

;again, if we create a key(/value property) called 'Length', the method fails:
obj.Length := "value"
MsgBox, % obj.Length() ;(blank)
MsgBox, % obj.Length ;value

;if we create a func object based on ObjLength,
;and assign that to the key(/value property) called 'Length',
;that is another way to get a working method again:
obj.Length := Func("ObjLength")
MsgBox, % obj.Length() ;3
MsgBox, % obj.Length ;(blank)
- Some code to store functions/methods as object methods:

Code: Select all

class MyMethodClass
{
	MyMethod(vValue)
	{
		MsgBox, % vValue
	}
}

MyFunc(oObj, vValue)
{
	MsgBox, % vValue
}

MyMethodClass.MyMethod(1)

MyMethodClass.MyMethod2 := MyMethodClass.MyMethod
MyMethodClass.MyMethod2(2)

oMtd := MyMethodClass.MyMethod
MyMethodClass.MyMethod3 := oMtd
MyMethodClass.MyMethod3(3)

MyMethodClass.MyMethod4 := ObjRawGet(MyMethodClass, "MyMethod")
MyMethodClass.MyMethod4(4)

MyMethodClass.MyMethod5 := Func("MyFunc")
MyMethodClass.MyMethod5(5)

MyMethodClass.MyMethod6 := Func("MyMethodClass.MyMethod")
MyMethodClass.MyMethod6(6)
return
- Some code to store dynamic properties as object properties:

Code: Select all

class MyPropertyClass
{
	MyProperty
	{
		get
		{
			;MsgBox, % A_ThisFunc
			return "MyPropertyValue"
		}
		set
		{
		}
	}
}

;copy property from a class to an array

oProp := ObjRawGet(MyPropertyClass, "MyProperty")

MsgBox, % IsObject(oProp) ;1
MsgBox, % IsFunc("MyPropertyClass.MyProperty.get") ;2
MsgBox, % IsFunc("MyPropertyClass.MyProperty.set") ;3

;assign a property to an object:
obj := {}
;MsgBox, % obj.MyProperty ;(blank) ;as expected
oProp := ObjRawGet(MyPropertyClass, "MyProperty")
ObjRawSet(obj, "MyProperty", oProp)
MsgBox, % obj.MyProperty ;MyPropertyValue

;assign a property to an object:
obj := {}
;MsgBox, % obj.MyProperty ;(blank) ;as expected
for vKey, vValue in MyPropertyClass
{
	if (vKey = "MyProperty")
		ObjRawSet(obj, "MyProperty", vValue)
}
MsgBox, % obj.MyProperty ;MyPropertyValue

;==============================

;assign a getter to an object:
obj := {}
;MsgBox, % obj.MyGetter ;(blank) ;as expected
oGetter := Func("MyPropertyClass.MyProperty.get")
;oSetter := Func("MyPropertyClass.MyProperty.set")
obj.MyGetter := oGetter
MsgBox, % obj.MyGetter() ;MyPropertyValue
return
==================================================

[SECTION 3: META-FUNCTIONS]

> META-FUNCTIONS

- By this definition, __Call/__Get/__Set are meta-functions.
Objects - Definition & Usage | AutoHotkey
https://autohotkey.com/docs/Objects.htm
Meta-functions define what happens when a key is requested but not found within the target object.
- Although it is true that __Call is intended to handle calls to non-existent methods. The __Call function is also invoked when calls are made to methods that do exist.

- According to other sources, __Delete/__Init/__New are also meta-functions.
- We will refer to these in chapter headings as 'meta-functions+' (with a plus sign).
list of every object type/property/method - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=37&t=44081

- Which of these 6 methods is or isn't a 'meta-function', isn't especially important.
- The important thing to know is that they are all methods, and that they are generally used internally by the class, and are not normally referred to directly within a script.
- Creating custom methods called __Call/__Get/__Set or __Delete/__New gives you more control over your class.
- Note: __Init is undocumented and should not be used.

The 6 methods were introduced earlier, here is the same text again:
6 methods: __Init/__New/__Get/__Set/__Call/__Delete.
- The following 6 methods are key to customisation when creating custom classes.
•__Init ['should not be used by the script', handles when an object instance is created]
•__New [handles when an object instance is created]
•__Get / __Set [handle when a non-existent key/property is gotten/set]
•__Call [handles when an existent/non-existent method is called]
•__Delete [handles when an object instance is deleted]
- Note: __Get/__Set are only invoked when certain items don't exist.
- If you want to log each time a key or property is gotten or set, there are workarounds, discussed below.
- Note: __Call can be used to log each time a method is called.

- Here is an example script, where a MsgBox reports each time a method is invoked.

Code: Select all

class MyNotifyClass
{
	__Init() ;new object created
	{
		MsgBox, % A_ThisFunc
	}
	__New() ;new object created
	{
		MsgBox, % A_ThisFunc
	}
	__Get() ;attempt to retrieve the value of a key that doesn't exist
	{
		MsgBox, % A_ThisFunc
	}
	__Set() ;attempt to set the value of a key that doesn't exist
	{
		MsgBox, % A_ThisFunc
	}
	__Call() ;attempt to use a method
	{
		MsgBox, % A_ThisFunc
	}
	__Delete() ;object is deleted
	{
		MsgBox, % A_ThisFunc
	}
	MyMethod()
	{
	}
}

obj := new MyNotifyClass ;__Init and __New

var := obj["key"] ;__Get (if key doesn't exist)
obj["key"] := "value" ;__Set (if key doesn't exist)
var := obj["key"] ;note: __Get not called, since the key already exists
obj["key"] := "value" ;;note: __Get not called, since the key already exists

obj.NotAMethod() ;__Call ;__Call invoked for non-existent method
obj.MyMethod() ;__Call ;__Call invoked for existent method

obj := "" ;__Delete
- Note: A line like 'obj := ""', will sometimes delete an object, sometimes it won't.
- If prior to 'obj := ""', the object pointed to by 'obj' has a reference count of 1, the object is deleted. The __Delete method is invoked.
- If prior to 'obj := ""', the object pointed to by 'obj' has a reference count greater than 1, the object's reference count is decreased by 1.

Code: Select all

obj1 := {}
obj2 := obj1
obj3 := obj1
;ObjAddRef increases the reference count by 1
;ObjRelease decreases the reference count by 1 and returns the current count
MsgBox, % Format("{2}", ObjAddRef(&obj1), ObjRelease(&obj1)) ;3
obj3 := ""
MsgBox, % Format("{2}", ObjAddRef(&obj1), ObjRelease(&obj1)) ;2
obj2 := ""
MsgBox, % Format("{2}", ObjAddRef(&obj1), ObjRelease(&obj1)) ;1
obj1 := "" ;object deleted
- Return:
- Some subtleties of using 'return' in one of the 6 methods.
- Here are 3 possible uses of 'return':
- end a function prematurely
- end a function prematurely, and prevent certain unseen actions from occurring
- output something
- For each of the 6 'meta-functions', the possible effects of 'return', will be discussed in the chapters that follow.

- Parameter counts (min/max):
__New() [min: 0, max: unlimited][e.g. 2 parameters: obj := new MyClass(param1, param2)]
__Get() [min: 0, max: unlimited][e.g. 2 parameters: var := obj["key1", "key2"]
__Set() [min: 0, max: unlimited][e.g. 3 parameters: obj["key1", "key2"] := "value"][parameters: key1, key2, value]
__Call() [min: 0, max: unlimited][e.g. 3 parameters var := obj.MyMethod(param1, param2)][parameters: MyMethod, param1, param2]
__Delete() [min: 0, max: unlimited]

- This example demonstrates two ways to define a class, function syntax (old school) and method syntax (new school):

Code: Select all

MyFunc(obj, params*)
{
	MsgBox, % A_ThisFunc
}

class MyClass
{
	;'obj' param not needed, use 'this' if needed, an implicit parameter
	__Delete(params*)
	{
		MsgBox, % A_ThisFunc
	}
}

fn := Func("MyFunc")
obj1 := new MyClass
obj2 := {}, obj2.base := {__Delete:fn}
obj1 := ""
obj2 := ""
- This link also demonstrates function and method syntax:
Objects - Definition & Usage | AutoHotkey
https://autohotkey.com/docs/Objects.htm#Meta_Functions

- Any normal function, intended as a method, should have one required parameter, and the other parameters should all be optional. Otherwise a call could fail (silently fail in AHK v1).
- An example demonstrating that any custom function used as a __Delete method should have 1 required parameter, and the rest optional.

Code: Select all

MyFunc1(obj, params*)
{
	MsgBox, % A_ThisFunc
}

MyFunc2(obj, param1, params*)
{
	MsgBox, % A_ThisFunc
}

fn1 := Func("MyFunc1")
fn2 := Func("MyFunc2")
obj1 := {}, obj1.base := {__Delete:fn1}
obj2 := {}, obj2.base := {__Delete:fn2}

;in AHK v1, we do not see a MsgBox for MyFunc2
;in AHK v2, we see an error
obj1 := ""
obj2 := "" ;Error:  Missing a required parameter. ;(AHK v2 error)
==================================================

> META-FUNCTIONS+: __NEW / __DELETE

- The __New method allows an action to be taken when an object is created.
- The __Delete method allows an action to be taken when an object is deleted.

Code: Select all

global g_CountObjCurrent := 0
global g_CountObjEver := 0
class MyCountClass
{
	__New()
	{
		g_CountObjCurrent++
		g_CountObjEver++
	}
	__Delete()
	{
		g_CountObjCurrent--
	}
}

MsgBox, % g_CountObjCurrent " " g_CountObjEver ;0 0
obj1 := new MyCountClass
MsgBox, % g_CountObjCurrent " " g_CountObjEver ;1 1
obj2 := new MyCountClass
MsgBox, % g_CountObjCurrent " " g_CountObjEver ;2 2
obj3 := new MyCountClass
MsgBox, % g_CountObjCurrent " " g_CountObjEver ;3 3
obj1 := ""
MsgBox, % g_CountObjCurrent " " g_CountObjEver ;2 3
obj2 := ""
MsgBox, % g_CountObjCurrent " " g_CountObjEver ;1 3
obj3 := ""
MsgBox, % g_CountObjCurrent " " g_CountObjEver ;0 3
- The __Delete method can be used as a hack that allows you to perform certain actions when an object is deleted.

Code: Select all

class MyTempSetSCSClass
{
	__New(vMode)
	{
		this.orig := A_StringCaseSense
		StringCaseSense, % vMode
	}
	__Delete()
	{
		StringCaseSense, % this.orig
	}
}

;we write 3 functions that remove capital a's:

;this function sets StringCaseSense to On, but doesn't restore it to its previous state:
Func1(vText)
{
	StringCaseSense, On
	return StrReplace(vText, "A")
}

;this function sets StringCaseSense to On, and then restores it to its previous state:
Func2(vText)
{
	vSCS := A_StringCaseSense
	StringCaseSense, On
	vRet := StrReplace(vText, "A")
	StringCaseSense, % vSCS
	return vRet
}

;like Func2, this function sets StringCaseSense to On, and then restores it to its previous state:
;(note: when return is done, obj is deleted, and the state is reset)
Func3(vText)
{
	obj := new MyTempSetSCSClass("On")
	return StrReplace(vText, "A")
}

vOutput := ""

StringCaseSense, Off
vOutput .= Func1("Aa") " " A_StringCaseSense "`r`n"

StringCaseSense, Off
vOutput .= Func2("Aa") " " A_StringCaseSense "`r`n"

StringCaseSense, Off
vOutput .= Func3("Aa") " " A_StringCaseSense "`r`n"

;we see that each function removed capital a's
;and that Func1 failed to reset A_StringCaseSense
MsgBox, % vOutput
- Note: if you use 'return' in a __Delete method, the object will still be deleted.
- Note: if you use 'return' in a __New method, that will override what object is created. You could even return a string instead of an object.

Code: Select all

class MyNewReturnClass1
{
	__New()
	{
		return ["a", "b", "c"]
	}
}
class MyNewReturnClass2
{
	__New()
	{
		return "hello"
	}
}
class MyNewReturnClass3
{
	__New()
	{
		return this
	}
}
class MyNewReturnClass4 ;equivalent to MyNewReturnClass3 (apart from the class name)
{
	__New()
	{
	}
}

obj1 := new MyNewReturnClass1
obj2 := new MyNewReturnClass2
obj3 := new MyNewReturnClass3
obj4 := new MyNewReturnClass4
MsgBox, % obj1.Length() ;3
MsgBox, % obj2 ;hello
MsgBox, % obj3.__Class
MsgBox, % obj4.__Class
- Two functionally equivalent classes that create a property when the object is created.
- One specifies the assignment in the main body of the class, the other within the __New method.

Code: Select all

class MyClass1
{
	prop := "value"
}

class MyClass2
{
	__New()
	{
		this.prop := "value"
	}
}

obj1 := new MyClass1
obj2 := new MyClass2
MsgBox, % obj1.prop
MsgBox, % obj2.prop
- An example of creating objects, demonstrating specifying parameters, and demonstrating creating a class using a dynamic name.

Code: Select all

class MyClass1
{
	__New()
	{
		MsgBox, % A_ThisFunc
	}
}

class MyClass2
{
	__New(vText1, vText2)
	{
		MsgBox, % A_ThisFunc "`r`n" vText1 " " vText2
	}
}

obj := new MyClass1
obj := new MyClass2("a", "b")

class1 := "MyClass1"
class2 := "MyClass2"
obj := new %class1%
obj := new %class2%("a", "b")
==================================================

> META-FUNCTIONS+: __INIT

- __Init is an undocumented method that is called when a script starts.
- Here are some experiments with __Init that demonstrate some of its features.

- __Init adds keys(/value properties) to instance objects:

Code: Select all

;this gives a 'Duplicate declaration' error:
class MyInitClass
{
	var := 1

	__Init()
	{
	}
}
- __Init does not add keys(/value properties) to the class object:

Code: Select all

;this does not give a 'Duplicate declaration' error:
class MyInitClass
{
	static var := 1

	__Init()
	{
	}
}
- __Init is called before __New:

Code: Select all

;__Init is called before __New:
class MyInitAndNewClass
{
	__Init()
	{
		MsgBox, % A_ThisFunc
	}
	__New()
	{
		MsgBox, % A_ThisFunc
	}
}
obj := new MyInitAndNewClass
- Defining a variable in the class body causes an __Init method to be present:

Code: Select all

;defining a variable in the class body
class MyPropNoClass
{
}
class MyPropYesClass
{
	prop := 1
}
class MyPropStaticClass
{
	static prop := 1
}

obj1 := new MyPropNoClass
obj2 := new MyPropYesClass
obj3 := new MyPropStaticClass

;==============================

;test MyPropNoClass:
vOutput := ""
for vKey, vValue in ObjGetBase(obj1)
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput

;output:
;__Class MyPropNoClass

;==============================

;test MyPropYesClass:
;a MyPropYesClass instance has an __Init class
vOutput := ""
for vKey, vValue in ObjGetBase(obj2)
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput ;(blank)

;output:
;__Class MyPropYesClass
;__Init

;==============================

;test MyPropStaticClass:
;a MyPropStaticClass instance does not have an __Init class
vOutput := ""
for vKey, vValue in ObjGetBase(obj3)
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput ;(blank)

;output:
;__Class MyPropStaticClass
;prop 1

;==============================
- Tests re. the __Class property:

Code: Select all

;note:
;class MyClass
;{
;}
;effectively does this:
;class MyClass
;{
;	static __Class := "MyClass"
;}

class MyAssignClassClass
{
	;static __Class := "hello" ;error: Duplicate declaration
	;__Class := "hello" ;error: Duplicate declaration
}
- This example gives an indication of what __Init does.
- Note: When you use Call, you must specify the object explicitly, the object is passed implicitly in various other contexts.
- An object is passed to the __Init method, a key is assigned to that object.

Code: Select all

class MyClass
{
	prop := "value"
}

MyClass.__Init.Call(obj:={})
MsgBox, % obj.prop
- Using 'return' appears to have no effect on __Init.
- The expected key(/value property) is created.

Code: Select all

class MyClass
{
	__Init()
	{
		this.prop := "value"
		return
	}
}

obj := new MyClass
MsgBox, % obj.prop

MyClass.__Init.Call(obj:={})
;MyClass.__Init.(obj:={}) ;deprecated syntax
MsgBox, % obj.prop
==================================================

> META-FUNCTIONS: __CALL

- __Call is a method that is called when the script tries to execute a non-existent (or existent) method.

Code: Select all

class MyCallClass
{
	__Call(vMethod)
	{
		MsgBox, % "attempt to call existent/non-existent method:`r`n" vMethod
	}
	MyMethod()
	{
	}
}

obj := new MyCallClass
obj.MyMethod()
obj.NotAMethod()
- One use for Call is to save code lines by combining multiple methods into one, instead of having multiple separate methods.

Code: Select all

class MyCaseClass1
{
	Upper(vText)
	{
		return Format("{:U}", vText)
	}
	Title(vText)
	{
		return Format("{:T}", vText)
	}
	Lower(vText)
	{
		return Format("{:L}", vText)
	}
}

class MyCaseClass2
{
	__Call(vMethod, vText)
	{
		if vMethod in % "Upper,Title,Lower"
			return Format("{:" SubStr(vMethod, 1, 1) "}", vText)
		else
			return
	}
}

obj1 := new MyCaseClass1
obj2 := new MyCaseClass2
MsgBox, % obj1.Upper("hello")
MsgBox, % obj2.Upper("hello")
- Another use for Call is to have case-sensitive methods names. Ordinarily in AutoHotkey, method names are case insensitive.

Code: Select all

class MyCallClass
{
	__Call(vMethod)
	{
		if (vMethod == "HELLO")
			return "HELLO WORLD"
		else if (vMethod == "Hello")
			return "Hello World"
		else if (vMethod == "hello")
			return "hello world"
	}
}
obj := new MyCallClass
MsgBox, % obj.HELLO() ;HELLO WORLD
MsgBox, % obj.Hello() ; Hello World
MsgBox, % obj.hello() ;hello world

;if a key is created with the same name as one of the anticipated values in the __Call method, this prevents the __Call method from being called:
ObjRawSet(obj, "hello", "MyValue")
MsgBox, % obj.HELLO() ;(blank)
MsgBox, % obj.Hello() ;(blank)
MsgBox, % obj.hello() ;(blank)
- This example demonstrates that every time something that looks like a method is called (i.e. a method name and parentheses, and optionally parameters), the __Call method is invoked.
- Even if a method (or property) exists, the __Call method is invoked.
- If a key(/value property) exists with the same name as the method, the __Call method is not invoked.
- One use of the __Call method is to monitor each time a method or property is called.

Code: Select all

class MyCallClass
{
	MyValueProperty := "MyValuePropertyValue"

	__Call(vMethod)
	{
		MsgBox, % A_ThisFunc "`r`n" "method called:`r`n" vMethod
		;return "x"
	}

	MyMethod()
	{
		return "MyMethodValue"
	}

	MyDynamicProperty
	{
		get
		{
			return "MyDynamicPropertyValue"
		}
		set
		{
		}
	}
}

obj := new MyCallClass

MsgBox, % obj.MyMethod() ;MyMethodValue ;__Call invoked
MsgBox, % obj.MyDynamicProperty() ;MyDynamicPropertyValue ;__Call invoked
MsgBox, % obj.MyValueProperty() ;(blank) ;since MyValueProperty is a key(/value property)
MsgBox, % obj.MyNonExistentEntity() ;(blank) ;__Call invoked

;note: since these calls lack parentheses, __Call won't be invoked:
MsgBox, % obj.MyMethod ;(blank) ;since parentheses omitted
MsgBox, % obj.MyDynamicProperty ;MyDynamicPropertyValue
MsgBox, % obj.MyValueProperty ;MyValuePropertyValue
MsgBox, % obj.MyNonExistentEntity ;(blank)
- If a 'return' is done inside the __Call method, then any method/property that would have been invoked, is not invoked.
- Also, the value specified by 'return' is then returned.

==================================================

> META-FUNCTIONS: __GET

- __Get is a method that is called when the script tries to retrieve the value of a non-existent key.
- Here we change the default value returned for a non-existent key from a blank string, to the number 0.
- The effect of 'return' in a __Get method is to specify a default value for when a key is not found.
- A default return value of 0 is useful for creating tallies (frequency counts).

Code: Select all

class MyGetClass
{
	__Get(vKey)
	{
		return 0
	}
}

obj1 := {}
obj2 := new MyGetClass
MsgBox, % obj1["NonExistentKey"] ;(blank)
MsgBox, % obj2["NonExistentKey"] ;0

obj1["key"]++
obj2["key"]++
MsgBox, % obj1["key"] ;(blank)
MsgBox, % obj2["key"] ;1

;otherwise to increment a key:
obj3 := {}
obj3["key"] := obj3.HasKey("key") ? obj3["key"]+1 : 1
MsgBox, % obj3["key"] ;1
- Here we use the Format function to change the default value for an array to 0.

Code: Select all

;here is a simple 'switch statement' via an object:

;using the HasKey method
obj := {a:"A", b:"B", c:"C"}
MsgBox, % obj.HasKey("a") ? obj["a"] : "ERROR"
MsgBox, % obj.HasKey("d") ? obj["d"] : "ERROR"

;==============================

;using a bound func:
obj := {a:"A", b:"B", c:"C", base:{__Get:Func("Format").Bind("{}", "ERROR")}}
MsgBox, % obj.a
MsgBox, % obj.d

;for reference:
;MsgBox, % Format("{}", "ERROR") ;ERROR

;the bound func approach is explained in more detail, here:
;jeeswg's object classes tutorial - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=7&t=54588&p=285624#p285624

;in short, what is done is:
;__Get is called for obj[key], when 'key' doesn't exist,
;we define __Get to invoke the Format function with 2 default parameters,
;and so the string 'ERROR' is returned
- Here we use the Format function to change the default value for an array to 0.

Code: Select all

obj1 := {}
obj2 := {base:{__Get:Func("Format").Bind("{}", 0)}}
MsgBox, % obj1.key ;(blank)
MsgBox, % obj2.key ;0
MsgBox, % obj2.key+3 ;3

;for reference:
;MsgBox, % Format("{}", 0) ;0
- This example demonstrates that the __Get method is only called for a key that doesn't exist:

Code: Select all

class MyGetClass
{
	__Get(vKey)
	{
		MsgBox, % A_ThisFunc
	}
}

obj := new MyGetClass
MsgBox, % obj["key"] ;(blank)
obj["key"] := "value"
MsgBox, % obj["key"] ;value
==================================================

> META-FUNCTIONS: __SET

- __Set is a method that is called when the script tries to set the value of a non-existent key.
- In this class we only allow positive integers to be assigned as keys.

Code: Select all

class MySetClass
{
	__Set(vKey, vValue)
	{
		if RegExMatch(vValue, "^\d+$")
			ObjRawSet(this, vKey, vValue)
		else
			MsgBox, % "error: invalid value:`r`n" vValue
		return ;without this line the class would assign this[vKey] := vValue
	}
}

obj := new MySetClass
obj["key1"] := 123
obj["key2"] := "abc"
MsgBox, % obj["key1"] ;123
MsgBox, % obj["key2"] ;(blank)
- Note: if we tried to assign a key like this, inside the class:
this[vKey] := vValue
instead of this:
ObjRawSet(this, vKey, vValue)
that would trigger the __Set method in an infinite loop.
- ObjRawSet bypasses the __Set method.

==================================================

> __GET/__SET: MONITOR EVERY TIME A KEY IS GOTTEN/SET

- Here are workarounds to, in effect, invoke __Get/__Set even if the key already exists.
- Here are two ways to be able to monitor each time get/set is done.

- In this example we use a global object.
- Any gets/sets are done to an external object, and since no keys are created in the instance object, the __Get/__Set meta-functions will keep getting invoked.

Code: Select all

global oData := {}
class MyClass
{
	__New()
	{
		;note: 'this' is the instance object
		;note: '&this' is the address of the instance object
		global oData
		oData[&this] := {}
	}
	__Get(vKey)
	{
		global oData
		MsgBox, % A_ThisFunc "`r`n" vKey "=" oData[&this, vKey]
		return oData[&this, vKey]
	}
	__Set(vKey, vValue)
	{
		global oData
		oData[&this, vKey] := vValue
		MsgBox, % A_ThisFunc "`r`n" vKey "=" vValue
		return ;without this line, a key would be automatically assigned to the instance object
	}
	__Delete()
	{
		global oData
		oData.Delete(&this)
	}
}

obj := new MyClass

;the __Set method is invoked each time:
Loop 3
	obj["key"] := "value" A_Index

;the __Get method is invoked each time:
Loop 3
	MsgBox, % obj["key"]

;==============================

;check how many instance objects currently exist:
obj2 := new MyClass
obj3 := new MyClass

MsgBox, % oData.Count() ;3
obj := ""
MsgBox, % oData.Count() ;2
obj2 := ""
obj3 := ""
MsgBox, % oData.Count() ;0
- In this example we use a class object to store data for all of its instance objects.
- This is probably a better approach, because generally in programming it is undesirable to use global variables.
- (Global variable names clutter the namespace, and you risk 2 functions that use the same global variable name, using the same variable.)
- (So, it's better for things to be self-contained.)

Code: Select all

class MyClass
{
	static Data := {}
	__New()
	{
		ObjRawSet(MyClass.Data, &this, {})
	}
	__Get(vKey)
	{
		MsgBox, % A_ThisFunc "`r`n" vKey "=" MyClass.Data[&this, vKey]
		return MyClass.Data[&this, vKey] ;does work
		;return this.base.Data[&this, vKey] ;doesn't work
	}
	__Set(vKey, vValue)
	{
		MyClass.Data[&this, vKey] := vValue
		MsgBox, % A_ThisFunc "`r`n" vKey "=" vValue
		return ;without this line a key would be automatically assigned to the instance object
	}
	__Delete()
	{
		MyClass.Data.Delete(&this)
	}
}

obj := new MyClass

;the __Set method is invoked each time:
Loop 3
	obj["key"] := "value" A_Index

;the __Get method is invoked each time:
Loop 3
	MsgBox, % obj["key"]

;==============================

;check how many instance objects currently exist:
obj2 := new MyClass
obj3 := new MyClass

MsgBox, % MyClass.Data.Count() ;3
obj := ""
MsgBox, % MyClass.Data.Count() ;2
obj2 := ""
obj3 := ""
MsgBox, % MyClass.Data.Count() ;0
- It's also possible to store data in property getters/setters and methods by using static variables.
- The property getters/setters and methods are just functions. And any static variables inside those functions are unique to the functions, but accessible to any instance objects that use those property getters / property setters / methods.
- One issue is that property getters and setters are separate functions, so they can't share data unless a common variable is used e.g. a global variable.
- Another issue is that you might want a way to delete keys from those static variables, something you might want to do when an object instance is deleted.
- The script below stores the time at which a class is created, it stores this info inside a method and inside a property getter.

Code: Select all

class MyClass
{
	__New()
	{
		;call the method and property when creating the object:
		this.MyMethod()
		var := this.MyProperty
	}
	MyMethod()
	{
		static oData := {}
		if !oData.HasKey(&this)
			oData[&this] := A_Now
		return oData[&this]
	}

	MyProperty
	{
		get
		{
			static oData := {}
			if !oData.HasKey(&this)
				oData[&this] := A_Now
			return oData[&this]
		}
		set
		{
		}
	}
}

;the instance creation date is stored in a method/property:
obj1 := new MyClass
Sleep, 1000
obj2 := new MyClass
vOutput := obj1.MyMethod() "`r`n" obj1.MyProperty "`r`n"
vOutput .= obj2.MyMethod() "`r`n" obj2.MyProperty "`r`n"
MsgBox, % vOutput
==================================================

> __GET/__SET: ADD KEYS AND SUBKEYS (MULTI-DIMENSIONAL ASSIGNMENTS)

- The documentation mentions such assignments, like so:
Objects - Definition & Usage | AutoHotkey
https://autohotkey.com/docs/Objects.htm#Usage_Arrays_of_Arrays
Multi-dimensional assignments such as table[a, b, c, d] := value
- There is a difference between the following:
var := obj.a.b.c
var := obj["a", "b", "c"]
when certain keys(/value properties) don't already exist.
- If obj.a.b.c does not exist, it fails immediately.
- obj["a", "b", "c"] checks obj.a, then obj.a.b, then obj.a.b.c.
- This example demonstrates the difference:

Code: Select all

class MyClass
{
	__Get(oParams*)
	{
		vOutput := ""
		for vKey, vValue in oParams
			vOutput .= vKey " " vValue "`r`n"
		MsgBox, % A_ThisFunc "`r`n" vOutput

		if (oParams.1 = "g") ;diagnostic
			return {h:{i:"MyValue"}} ;diagnostic
	}
	__Set(oParams*)
	{
		vOutput := ""
		for vKey, vValue in oParams
			vOutput .= vKey " " vValue "`r`n"
		MsgBox, % A_ThisFunc "`r`n" vOutput
	}
}

obj := new MyClass

;there is a difference in function between the following 2 syntaxes:
MsgBox, % obj["a", "b", "c"] ;(blank)
;MsgBox, % obj.d.e.f ;Error:  No object to invoke. ;(AHK v2 error)

;an unusual condition was added to the class to handle 'obj.g'
MsgBox, % obj.g.h.i ;MyValue

MsgBox

;again, there is a difference in function between the following 2 syntaxes:
obj["a", "b", "c"] := "KeyABC"
;obj.d.e.f := "KeyDEF" ;Error:  No object to invoke. ;(AHK v2 error)
;obj.d.e := "KeyDE" ;Error:  No object to invoke. ;(AHK v2 error)
obj.d := "KeyD"
MsgBox, % obj.a.b.c ;KeyABC
MsgBox, % obj.d ;KeyD
- When creating keys and subkeys, the classes have to be written carefully.
- One thing that needs to be considered is that when an object is created as a subkey of an object. Should the child object have the same class as the parent object, or should it be an AHK basic object.
- The documentation describes the issue, like so:
Objects - Definition & Usage | AutoHotkey
https://autohotkey.com/docs/Objects.htm#Subclassing_aoa
When a multi-parameter assignment such as table[x, y] := content implicitly causes a new object to be created, the new object ordinarily has no base and therefore no custom methods or special behaviour.
- This example demonstrates two classes: one where the child objects are basic AHK objects, and one where the child objects have the same class as the parent object.

Code: Select all

class MyClass
{
}

class MyClass2
{
	__Set(oParams*)
	{
		;MsgBox, % StrJoin("`r`n", oParams*)
		if (oParams.Length() = 2)
		{
			ObjRawSet(this, oParams.1, oParams.2)
			return oParams.2
		}
		else if (oParams.Length() < 2)
			return
		vValue := oParams.Pop()
		ObjRawSet(this, oParams.1, new MyClass2)
		return this[oParams*] := vValue
	}
}

obj := new MyClass
obj["a", "b", "c", "d", "e"] := "value"

vOutput := ""
vOutput .= obj.__Class "`r`n"
vOutput .= obj.a.__Class "`r`n"
vOutput .= obj.a.b.__Class "`r`n"
vOutput .= obj.a.b.c.__Class "`r`n"
vOutput .= obj.a.b.c.d.__Class "`r`n"
MsgBox, % vOutput

obj := new MyClass2
obj["a", "b", "c", "d", "e"] := "value"

vOutput := ""
vOutput .= obj.__Class "`r`n"
vOutput .= obj.a.__Class "`r`n"
vOutput .= obj.a.b.__Class "`r`n"
vOutput .= obj.a.b.c.__Class "`r`n"
vOutput .= obj.a.b.c.d.__Class "`r`n"
MsgBox, % vOutput
==================================================

> __GET/__SET: GET AND SET AT THE SAME TIME

- 'When is a get not a get? When it's a set.'
- We consider what is returned when you do var := obj["key"] := value
- We have a set: obj["key"] := value
- And a get: var := obj["key"]

Code: Select all

class MyGetSetClass
{
	__Get(k)
	{
		return "key not found"
	}
	__Set(k, v)
	{
		ObjRawSet(this, k, v)
		return "key given initial value"
	}
}

;__Get output v. __Set output
obj := new MyGetSetClass
var1 := obj["key"]
var2 := obj["key"] := "MyValue" ;it appears to get and set at the same time
var3 := obj["key"]
var4 := obj["key"] := "MyValue" ;it appears to get and set at the same time
var5 := obj["key"]
MsgBox, % var1 ;key not found
MsgBox, % var2 ;key given initial value
MsgBox, % var3 ;MyValue
MsgBox, % var4 ;MyValue ;not 'key given initial value' because __Set is only invoked for a key that does not exist
MsgBox, % var5 ;MyValue
==================================================

> GENERAL CLASS EXAMPLES: DATES AND SPLIT PATH

Code: Select all

class MyDateClass
{
	__New(vDate:="")
	{
		if (vDate = "")
		{
			ObjRawSet(this, "date", A_Now)
		}
		else
		{
			;expand truncated dates e.g. 'yyyyMMdd' to 14 characters:
			FormatTime, vDate, % vDate, yyyyMMddHHmmss
			if vDate is not date
				return ;a blank string is returned, no object is returned
			ObjRawSet(this, "date", vDate)
		}
	}
	Format(vFormat)
	{
		FormatTime, vDate, % vDate, yyyyMMddHHmmss
		return vDate
	}
	Add(vNum, vUnit, vFormat="yyyyMMddHHmmss")
	{
		vDate1 := this.date
		EnvAdd, vDate1, % vNum, % vUnit
		if !(vFormat == "yyyyMMddHHmmss")
			FormatTime, vDate1, % vDate1, % vFormat
		return vDate1
	}
	Diff(vDate2, vUnit)
	{
		vDate1 := this.date
		EnvSub, vDate1, % vDate, % vUnit
		return vDate1
	}
	__Get(vKey)
	{
		FormatTime, vDate, % this.date, % vKey
		return vDate
	}
	__Set()
	{
		return
	}
}

;create a date object for the current date and time:
oDate := new MyDateClass
MsgBox, % oDate.date
MsgBox, % oDate.yyyy
MsgBox, % oDate.ddd " " oDate["HH:mm:ss dd/MM/yyyy"]
MsgBox, % oDate.Add(365, "d", "ddd") " " oDate.Add(365, "d", "HH:mm:ss dd/MM/yyyy")

;create a date object for a specific date:
oDate.date := 20030303030303
MsgBox, % oDate.ddd " " oDate["HH:mm:ss dd/MM/yyyy"]
oDate := ""

;create a date object for a specific date:
oDate := new MyDateClass(20060504030201)
MsgBox, % oDate.date
MsgBox, % oDate.ddd " " oDate["HH:mm:ss dd/MM/yyyy"]

;note: if we delete the 'return' line in __Set,
;we can write arbitrary keys:
;oDate["abc"] := "hello"
;MsgBox, % oDate["abc"]
- Another example:
[split path class cf. SplitPath command]
object classes: coding style - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=48938

==================================================

[SECTION 4: ENUMERATORS AND MISCELLANEOUS]

> ENUMERATORS: _NEWENUM AND NEXT

- There are two important methods relating to looping through each item in an object: _NewEnum and Next.
- When a for loop is applied to an object, the object's _NewEnum method is invoked. The method creates an enumerator object which is passed to the for loop.
- The enumerator object needs one method only, a Next method.
- The Next method determines which key/value pairs are output during the for loop, and in which order. It does this by specifying two ByRef variables: key and value.
- It could output a number of ByRef variables other than 2.
- Also, the Next method determines when the for loop ends. It does this by specifying 0, as the method's (function's) return value, to end the loop, else a non-zero integer (for the loop to keep going).
- Note: often an enumerator object class will have two methods: a __New method, to prepare the information, and a Next method, to output it.

- In this example, we try to recreate the functionality of the AHK basic object, as regards for loops.
- Note: two ways to loop through objects are: a for loop, and, using a Next method directly. Both are demonstrated in the example.
- (Another way to loop, for linear arrays, e.g. 'obj', is to retrieve obj.Length() and loop through var := obj[A_Index].)

Code: Select all

class MyClass
{
	_NewEnum()
	{
		MsgBox, % A_ThisFunc
		return new MyEnumClass(this)
	}
}

class MyEnumClass
{
	__New(obj)
	{
		MsgBox, % A_ThisFunc
		this.data := obj
		this.temp := []

		;we can't do this:
		;because 'for k in obj' would request an enumerator
		;from MyEnumClass, causing an infinite loop
		;for k in obj
		;	this.temp.Push(k)

		;instead we do this:
		oEnum := ObjNewEnum(obj)
		while oEnum.Next(k)
			this.temp.Push(k)

		this.count := this.temp.Length()
		this.index := 1
	}
	Next(ByRef k:="", ByRef v:="")
	{
		if (this.index > this.count)
			MsgBox, % A_ThisFunc
		if (this.index > this.count)
			return 0
		k := this.temp[this.index]
		v := this.data[k]
		this.index++
		MsgBox, % A_ThisFunc "`r`n" k " " v
		return 1
	}
}

oArray := new MyClass
oArray.q := "Q", oArray.w := "W", oArray.e := "E"
for vKey, vValue in oArray
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput
- Using the Enum method directly, makes it possible to do 2 simultaneous 'for loops'.
- Loop through 2 objects at the same time.

Code: Select all

oArray1 := {a:1, b:2, c:3}
oArray2 := {d:4, e:5, f:6, g:7, h:8}
oEnum1 := ObjNewEnum(oArray1)
oEnum2 := ObjNewEnum(oArray2)

vOutput := ""
Loop
{
	if vRet1 := oEnum1.Next(vKey, vValue)
		vOutput .= vKey " " vValue "`r`n"
	if vRet2 := oEnum2.Next(vKey, vValue)
		vOutput .= vKey " " vValue "`r`n"
	if !vRet1 && !vRet2
		break
}
MsgBox, % vOutput
- Custom enumerators can be used to create an infinite for loop.
- This example generates the square numbers.

Code: Select all

class MyClass
{
	_NewEnum()
	{
		return new MyEnumClass
	}
}

class MyEnumClass
{
	__New()
	{
		this.index := 1
	}
	Next(ByRef k:="", ByRef v:="")
	{
		k := this.index
		v := this.index ** 2
		this.index++
		return 1
	}
}

oArray := new MyClass
vOutput := ""
for vKey, vValue in oArray
{
	vOutput .= vKey " " vValue "`r`n"
	if (vKey = 20)
		break
}
MsgBox, % vOutput

;we can use a for loop on an enumerator object class like so:
for vKey, vValue in {_NewEnum:RetNewEnum}
{
	vOutput .= vKey " " vValue "`r`n"
	if (vKey = 20)
		break
}
MsgBox, % vOutput

RetNewEnum(obj)
{
	return new MyEnumClass
}
- Using the Next function allows a loop to generate more than 2 output values.
- This example generates squares, cubes and fourth powers.

Code: Select all

class MyEnumClass
{
	__New()
	{
		this.index := 1
	}
	Next(ByRef n1:="", ByRef n2:="", ByRef n3:="", ByRef n4:="")
	{
		n1 := this.index
		n2 := this.index ** 2
		n3 := this.index ** 3
		n4 := this.index ** 4
		this.index++
		return 1
	}
}

oEnum := new MyEnumClass
vOutput := ""
while oEnum.Next(vNum1, vNum2, vNum3, vNum4)
{
	vOutput .= Format("{}`t{}`t{}`t{}", vNum1, vNum2, vNum3, vNum4) "`r`n"
	if (A_Index = 20)
		break
}
MsgBox, % vOutput
- Here are some links to further enumerator examples.
- One thing to notice in the links is that the classes/methods needed for an enumerator object can appear in different places: e.g. as a separate class, as a nested class, within the class (alongside other methods).

[loop reverse, and, loop and delete]
objects: enumerator object queries - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=43499&p=230778#p230778

[numerical examples][enumerator methods within class]
enum type and while loop - Ask for Help - AutoHotkey Community
https://autohotkey.com/board/topic/78869-enum-type-and-while-loop/#entry500848
For loop question - Ask for Help - AutoHotkey Community
https://autohotkey.com/board/topic/66916-for-loop-question/#entry423515

[numerical example][enumerator as nested class]
objects: enumerator object queries - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=43499&p=230779#p230779

[an enumerator is specified by creating an array, i.e. a class defined via the old-school 'function syntax' approach: {methodname:"funcname"}]
find previous key in associated array - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=5489&p=31741#p31741

[passing an enumerator into a for loop]
notation: objects: key names to avoid clashes with methods/properties - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=43595&p=198159#p198159

[replace a basic object's enumerator with a custom enumerator]
How to create custom enumerator objects - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=7199

[Next example on a COM object]
AccViewer Basic - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=32039

==================================================

> SINGLETON

[slightly simpler example]
objects: backport AHK v2 Gui/Menu classes to AHK v1 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=37&t=43530&p=197457#p197457

[slightly more complex example]
object classes: redefine __Set() temporarily / general queries - Page 3 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=42674&p=198332#p198332

==================================================

> TUTORIALS / LINKS

[big collections of links]
object classes: redefine __Set() temporarily / general queries - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=42674&p=193931#p193931
objects: enumerator object queries - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=43499&p=198571#p198571

[investigating the binary of AHK objects]
object classes: redefine __Set() temporarily / general queries - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=42674&p=194533#p194533

[dictionary object attempt, where key names do not clash with method names]
Possible AHK bug - the word "base" is missed in an array for-loop - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=24107&p=113927#p113927

[advanced base example]
Parentheses when calling gets? - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=10482&p=58116#p58116

==================================================

> THE END

The long and binding road that leads to your for.

==================================================
Last edited by jeeswg on 01 Jan 2020, 15:11, edited 1 time in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
iseahound
Posts: 1434
Joined: 13 Aug 2016, 21:04
Contact:

Re: jeeswg's object classes tutorial

24 Aug 2018, 02:13

- The __New method allows an action to be taken when an object is created.
- The __Delete method allows an action to be taken before an object is deleted.
The __Delete() function is used to clean up resources allocated when the object was created. It should be avoided in an ideal situation.
- Note: if you use 'return' in a __Delete method, the object will still be deleted.
Note: The __Delete() function never returns a value.
That's right. Put any value you want in he return statement and you can never access it.

Property Syntax is "dirty" to be honest, it's an AutoHotkey unique feature.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: jeeswg's object classes tutorial

24 Aug 2018, 02:17

Hello jeeswg, impressive collection :shock:. Thanks for sharing :thumbup:.

I only sifted through the top content, I'm sure there is a lot of good information here.
- Key names can be of integer type (positive/0/negative) or string type.

Note,
Associative arrays wrote: Keys can be strings, integers or objects
(my bold)
Eg, o := {[]:1}

Cheers.
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: jeeswg's object classes tutorial

24 Aug 2018, 05:52

cool, i liked the custom enum bit. here's fibonacci(int overflow not handled):

Code: Select all

new InfiniteFibonacci()
Esc::ExitApp

class InfiniteFibonacci
{
	__New() {
		for index, fib in this._NewEnum()
			MsgBox % "index: " index "`nnumber: " fib
	}

	_NewEnum() {
		return new this.InfFibEnum()
	}

	class InfFibEnum
	{
		__New() {
			this.prevFib := 0
			this.nextFib := 1
			this.index := 0
		}

		Next(ByRef k := "", ByRef v := "") {
			if (this.index == 0)
				v := 0
			else
			{
				v := this.nextFib
				this.nextFib += this.prevFib
				this.prevFib := v
			}

			k := this.index++

			return 1
		}
	}
}
A_AhkUser
Posts: 1147
Joined: 06 Mar 2017, 16:18
Location: France
Contact:

Re: jeeswg's object classes tutorial

20 Jan 2019, 10:18

Great post, thanks for this jeeswg.
my scripts
User avatar
lmstearn
Posts: 688
Joined: 11 Aug 2016, 02:32
Contact:

Re: jeeswg's object classes tutorial

18 Jul 2019, 10:21

Bookmarked!
Spoiler
:arrow: itros "ylbbub eht tuO kaerB" a ni kcuts m'I pleH
hasantr
Posts: 933
Joined: 05 Apr 2016, 14:18
Location: İstanbul

Re: jeeswg's object classes tutorial

23 Jul 2019, 08:18

;using the HasKey method
obj := {a:"A",b:"B",c:"C"}
MsgBox, % obj.HasKey("a") ? obj["a"] : "ERROR"
MsgBox, % obj.HasKey("d") ? obj["d"] : "ERROR"

;using a bound func
obj := {a:"A",b:"B",c:"C",base:{__Get:Func("Format").Bind("{}", "ERROR")}}
MsgBox, % obj.a
MsgBox, % obj.d
and
global oData := {}
class MyClass
{
__New()
{
global oData
oData[&this] := {}
}
__Get(vKey)
{
global oData
MsgBox, % A_ThisFunc "`r`n" vKey "=" oData[&this, vKey]
return oData[&this, vKey]
}
__Set(vKey, vValue)
{
global oData
oData[&this, vKey] := vValue
MsgBox, % A_ThisFunc "`r`n" vKey "=" vValue
return ;without this line a key would be automatically assigned to the instance object
}
__Delete()
{
global oData
oData.Delete(&this)
}
}

obj := new MyClass
;the __Set method is invoked each time
Loop, 3
obj.key := "value" A_Index
;the __Get method is invoked each time
Loop, 3
MsgBox, % obj.key
;MsgBox, % oData.Count() ;1
;obj := ""
;MsgBox, % oData.Count() ;0

Red text is very difficult to understand. Which sources do I need help with?
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: jeeswg's object classes tutorial

23 Jul 2019, 09:03

CHANGING AN OBJECT'S DEFAULT VALUE

Here's a clarification. Yeah, it is pretty opaque, it combines a lot of ideas in one line. In my wish list, I've suggested we have a built-in function, perhaps 'ObjSetDefaultValue', so that that hack wouldn't be necessary.

Code: Select all

;==================================================

;CHANGING AN OBJECT'S DEFAULT VALUE

;we create 3 objects, each with keys a, b and c
;obj1 has a default value of a blank string
;obj2 and obj3 both have a default value of 'ERROR'
;obj3 uses a clever hack to recreate MyGetClass in one line

class MyGetClass
{
	;the __Get meta-function handles what happens
	;when the user does the equivalent of 'var := obj.mykey',
	;but 'mykey' doesn't exist

	__Get(vKey)
	{
		return "ERROR"
	}
}

obj1 := {a:"A", b:"B", c:"C"}

obj2 := new MyGetClass
obj2.a := "A"
obj2.b := "B"
obj2.c := "C"

obj3 := {a:"A", b:"B", c:"C", base:{__Get:Func("Format").Bind("{}", "ERROR")}}

MsgBox, % obj1.a " " obj1.d ;A
MsgBox, % obj2.a " " obj2.d ;A ERROR
MsgBox, % obj3.a " " obj3.d ;A ERROR

;==================================================

;EXPLAINING FUNC, BIND, BASE

;create a Func object:
oFunc := Func("Format")
MsgBox, % Format("{}", "ERROR")
MsgBox, % %oFunc%("{}", "ERROR") ;equivalent to line above

;create a BoundFunc object (with 1 parameter specified):
oFunc := Func("Format").Bind("{}")
MsgBox, % Format("{}", "ERROR")
MsgBox, % %oFunc%("ERROR") ;equivalent to line above

;create a BoundFunc object (with 2 parameters specified):
oFunc := Func("Format").Bind("{}", "ERROR")
MsgBox, % Format("{}", "ERROR")
MsgBox, % %oFunc%() ;equivalent to line above

;create an object with a custom base object
;the base object's __Get meta-function is defined as a BoundFunc object
;which simply returns the value 'ERROR'
obj3 := {base:{__Get:Func("Format").Bind("{}", "ERROR")}}

;note:
;the __Get meta-function handles what happens
;when the user does the equivalent of 'var := obj.mykey',
;but 'mykey' doesn't exist

;link:
;Func Object - Methods & Properties | AutoHotkey
;https://www.autohotkey.com/docs/objects/Func.htm

;==================================================
THE 'THIS' KEYWORD

Re. oData[&this, vKey], 'this' is a special word, mentioned here:
Objects - Definition & Usage | AutoHotkey
https://www.autohotkey.com/docs/Objects.htm
When thing.test() is called, thing is automatically inserted at the beginning of the parameter list.
On that page, see also, two examples: this.ptr and this.RGB.

'this' is an object reference, &this is the object's address.
In a function, you specify the object explicitly.
In a method, you don't specify the object explicitly, it is an implicit first parameter, accessible by using 'this'.
This is an example of 'this':

Code: Select all

class MyThisClass
{
	MyMethod()
	{
		MsgBox, % this.Length()
		MsgBox, % &this
	}
}

;similar to MyMethod, but with an explicit obj parameter,
;rather than an implicit 'this' parameter
MyFunc(obj)
{
	MsgBox, % obj.Length()
	MsgBox, % &obj
}

oArray := new MyThisClass
oArray.Push("a", "b", "c")
MsgBox, % oArray.Length() ;3
MsgBox, % &oArray ;(address)
oArray.MyMethod() ;will give the same values: 3 and (address)

oArray := ["a", "b", "c"]
oArray.base := {MyMethod:Func("MyFunc")}
MsgBox, % oArray.Length() ;3
MsgBox, % &oArray ;(address)
MyFunc(oArray) ;will give the same values: 3 and (address)
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
hasantr
Posts: 933
Joined: 05 Apr 2016, 14:18
Location: İstanbul

Re: jeeswg's object classes tutorial

23 Jul 2019, 12:19

Thank you very much jeeswg. I've been studying this topic line by line for days. Thanks to you I learned what I couldn't even learn in other programming languages. The contribution was great. Your work is really great. Thank you so much for your precious time.
SOTE
Posts: 1426
Joined: 15 Jun 2015, 06:21

Re: jeeswg's object classes tutorial

23 Jul 2019, 12:54

I agree too. Jeeswg, well done!
User avatar
rediffusion
Posts: 58
Joined: 15 Mar 2019, 05:16

Re: jeeswg's object classes tutorial

22 Sep 2019, 16:06

Hi guys how are you!?
I'm using «Total Commander».
I am a beginner and the internet has tons of information. It's hard to understand and find what I what so I ask you a question?
I surfed the internet and found these references:

stackoverflow.com /questions/36336511/autohotkey-script-that-edits-selected-text-without-using-the-clipboard
stackoverflow.com /questions/36067563/get-selected-text-without-using-the-clipboard
Note! Links to StackOverflow all are broken and I don't know why? :wtf:
Clipboard and ClipboardAll
WinExist() / IfWin[Not]Exist
WinGetText
Classes in AHK, Basic tutorial.

····························································································································
How do I get caught up in a class and get its text for further matching :?:
My plans... :idea:
Note:
1. I find this [Class] Wnd=0x000403E6
Also, I have some other info about the target field. I don't know maybe ID is better or Long?
How to know if it open at the moment? I'm interested in the folder/tab field.
Note! The field that contains text is clickable (need one click to copy text, if not clicked we can't copy).
2. Edit selected text `Корзина`.
3. If text "Корзина" exist then (by the way... some text exist always in that field).
4. GetText
5.
Note:

Code: Select all

ClipSaved := ClipboardAll   ; Save the entire clipboard to a variable of your choice.
; ... here make temporary use of the clipboard, such as for pasting Unicode text via Transform Unicode ...
ClipSaved := ClipboardAll
if (ClipSaved1 = ClipSaved2) then

some code bla bla bla...

Clipboard := ClipSaved   ; Restore the original clipboard. Note the use of Clipboard (not ClipboardAll).
ClipSaved := ""   ; Free the memory in case the clipboard was very large.

some code bla bla bla...

Else return or continue   ; if we didn't get the text of "Корзина".
User avatar
lmstearn
Posts: 688
Joined: 11 Aug 2016, 02:32
Contact:

Re: jeeswg's object classes tutorial

21 Oct 2019, 06:35

Just a thing on why __Init() will never be a popular choice in scripts- if there are any class variables defined there's a duplicate declaration warning:

Code: Select all

class MyClass
{
; ==>
classVariable := 0
; <==
	__Init()
	{
		this.key := "value"
		return
	}
}
obj := new MyClass
MsgBox, % obj.key

MyClass.__Init.Call(obj:={})
;MyClass.__Init.(obj:={}) ;deprecated syntax
MsgBox, % obj.key
It's also worth noting this comment on class variables:
A "class variable" is also not a variable in the same sense as local and global variables... I probably should have called them "instance assignments" and "class assignments" rather than "instance variable declarations" and "class variable declarations".
:arrow: itros "ylbbub eht tuO kaerB" a ni kcuts m'I pleH
sikongshan
Posts: 17
Joined: 06 Jul 2019, 21:57

Re: jeeswg's object classes tutorial

27 Dec 2019, 21:04

Thank you very much,The tutorial is very useful.
LeSeb
Posts: 17
Joined: 25 Oct 2017, 14:50

Re: jeeswg's object classes tutorial

22 Jun 2023, 07:19

Hello,

Thank you for all this work, very usefull to understand ( or at least try to) many concepts involved in classes

In this example :
jeeswg wrote:
23 Jul 2019, 09:03
CHANGING AN OBJECT'S DEFAULT VALUE
...

Code: Select all

class MyThisClass
{
	MyMethod()
	{
		MsgBox, % this.Length()
		MsgBox, % &this
	}
}

;similar to MyMethod, but with an explicit obj parameter,
;rather than an implicit 'this' parameter
MyFunc(obj)
{
	MsgBox, % obj.Length()
	MsgBox, % &obj
}

oArray := new MyThisClass
oArray.Push("a", "b", "c")
MsgBox, % oArray.Length() ;3
MsgBox, % &oArray ;(address)
oArray.MyMethod() ;will give the same values: 3 and (address)

oArray := ["a", "b", "c"]
oArray.base := {MyMethod:Func("MyFunc")}
MsgBox, % oArray.Length() ;3
MsgBox, % &oArray ;(address)
MyFunc(oArray) ;will give the same values: 3 and (address)
Why is the last sentence :

Code: Select all

MyFunc(oArray)
instead of

Code: Select all

oArray.MyMethod()
?

Is this a mistake or is it on purpose ?

Thank you!

Return to “Tips and Tricks (v1)”

Who is online

Users browsing this forum: No registered users and 22 guests