AHK v2 GUI SubClass-ing example (and any other classes?) Topic is solved

Get help for the alpha version of AutoHotkey v2 here. Please state the v2 version you are working with in the title when making a new topic.
User avatar
TheArkive
Posts: 435
Joined: 05 Aug 2016, 08:06
GitHub: TheArkive

AHK v2 GUI SubClass-ing example (and any other classes?)

Post by TheArkive » 23 Jan 2021, 13:36

Just wondering how exactly this works, and mainly if it is possible to extend the methods of a Gui obj or GuiControl obj. Or is this ONLY for event sinks?

I'm afraid I'm not even sure where to begin, except for this:

Code: Select all

class Gui2 extends Gui {
	...
}
... I assume.

I read that super.__new(opt, text, this) is part of this, but I haven't yet found any examples of this, and how it is supposed to be used. If I had to guess...

Code: Select all

class Gui2 extends Gui {
	__New(my, params, here) {
		super.__new(opt, text, this) ; opt = Gui opts? ... text = the title? ... this = subclass (Gui2 in this case)?
	}
	; other fancy stuff
}
Am I even close? And the main thing, can I add methods to the Gui class or the GuiControl class? (Or are there other built-in classes that work this way?)

My main use (so far) is to add .IsChecked(row) to a ListView control. I have other ways to do this of course, but I'm trying to learn deeper aspects of AHK. And I'm trying to learn a direct way of doing this, instead of just making a whole class wrapper (which i assume would be overkill?).

Is this stuff documented and I still missed it? I read up on super in the docs, but this seems to apply more to user defined classes/subclasses.

Thanks for any insight.
User avatar
kczx3
Posts: 1250
Joined: 06 Oct 2015, 21:39

Re: AHK v2 GUI SubClass-ing example (and any other classes?)

Post by kczx3 » 23 Jan 2021, 18:07

You can’t extend GuiControl yet afaik
User avatar
TheArkive
Posts: 435
Joined: 05 Aug 2016, 08:06
GitHub: TheArkive

Re: AHK v2 GUI SubClass-ing example (and any other classes?)

Post by TheArkive » 24 Jan 2021, 03:47

Can methods be added to a Gui class with subclassing?
User avatar
kczx3
Posts: 1250
Joined: 06 Oct 2015, 21:39

Re: AHK v2 GUI SubClass-ing example (and any other classes?)

Post by kczx3 » 24 Jan 2021, 08:27

Have you tried? Yes, you can add any methods you’d like to your class that extends from GUI.
User avatar
TheArkive
Posts: 435
Joined: 05 Aug 2016, 08:06
GitHub: TheArkive

Re: AHK v2 GUI SubClass-ing example (and any other classes?)  Topic is solved

Post by TheArkive » 24 Jan 2021, 10:31

kczx3 wrote:
24 Jan 2021, 08:27
Have you tried? Yes, you can add any methods you’d like to your class that extends from GUI.
My bad. For some reason my brain was grid-locked, until you asked if I tried.

I actually did try, but i way over complicated it on my first test, and wasn't sure where to go from there. Below worked. Thanks for helping me unclog the brain works :-P :facepalm:

I also managed to figure out how the event sink actually works. Not sure why that eluded me for so long.

Code: Select all

Global g

class gui2 extends gui {
    test2 := "property added"
    test3 := super.__Class ; super class name (Gui)
    
    __New(opt:="", title:="") {
        super.__New(opt, title, this)           ; Specify the event sink for the GUI obj as this subclass.
        this.msg := ObjBindMethod(this,"msg")   ; Required when func obj is needed and using a user class.
    }
    test() {
        return "method added"
    }
    some_event(ctl) {                   ; the "event sink" for WM_COMMAND notifications, used with Gui "event sink" (EventObj)
        this["MyEdit4"].Value := "Title: " ctl.Name "`r`n`r`n" ctl.value 
    }
    gui_close(*) {                      ; embedded method for close event; used with Gui "event sink"
        Msgbox "Now closing"
        ExitApp
    }
    msg(wParam, lParam, msg, hwnd) {    ; embedded OnMessage method, used with Gui "event sink"
        this["MyEdit2"].Value := "Mouse Move / wParam: " wParam "`r`nlParam: " lParam " / hwnd: " hwnd
    }
    Call(wParam, lParam, msg, hwnd) {   ; Custom user function when func obj is needed.
        state := (wParam) ? "Down" : "Up"
        this["MyEdit3"].Value := "LB " state " / wParam: " wParam "`r`nlParam: " lParam " / hwnd: " hwnd
    }
}

g := gui2.New(,"Test Gui")
OnMessage(0x200,g.msg) ; WM_MOUSEMOVE
OnMessage(0x201,g) ; WM_LBUTTONDOWN
OnMessage(0x202,g) ; WM_LBUTTONUP
g.OnEvent("close","gui_close")
g.Add("Edit","vMyEdit1 w200 r4").OnCommand(0x300,"some_event") ; same as "Change" for OnEvent ; EN_CHANGE
g.Add("Edit","vMyEdit2 ReadOnly w200 r2")
g.Add("Edit","vMyEdit3 ReadOnly w200 r2")
g.Add("Edit","vMyEdit4 ReadOnly w200 h100")

g.Show()

F2:: {
    txt := "test method:            " g.test() "`r`n"
    txt .= "test property:           " g.test2 "`r`n"
    txt .= "super class name:    " g.test3
    msgbox txt
}
EDIT: I guess this was my "RTFM moment".
User avatar
kczx3
Posts: 1250
Joined: 06 Oct 2015, 21:39

Re: AHK v2 GUI SubClass-ing example (and any other classes?)

Post by kczx3 » 24 Jan 2021, 11:41

Well done 😁
User avatar
TheArkive
Posts: 435
Joined: 05 Aug 2016, 08:06
GitHub: TheArkive

Re: AHK v2 GUI SubClass-ing example (and any other classes?)

Post by TheArkive » 25 Jan 2021, 05:50

I found a round-about way to add methods to controls. I made a small little example class wrapper as a proof of concept.

This of course is not a "true" extension of a control class, but it certainly works!

The cool thing about this, is that it's completely transparent.

Unlike extending the Gui class, you don't have to directly use the GuiCtl class... it's automatic.

Code: Select all

Global g

class gui2 extends gui {
    test2 := "property added"
    test3 := super.__Class ; super class name (Gui)
    
    __New(opt:="", title:="") {
        super.__New(opt, title, this)           ; Specify the event sink for the GUI obj as this subclass.
        this.msg := ObjBindMethod(this,"msg")   ; Required when func obj is needed and using a user class.
    }
    Add(type, options:="", value:="") {
        ctl := super.Add(type, options, value)
        return GuiCtl.New(ctl)
    }
    test() {
        return "method added"
    }
    some_event(ctl) {                   ; the "event sink" for WM_COMMAND notifications, used with Gui "event sink" (EventObj)
        this["MyEdit4"].Value := "Title: " ctl.Name "`r`n`r`n" ctl.value 
    }
    gui_close(*) {                      ; embedded method for close event; used with Gui "event sink"
        Msgbox "Now closing"
        ExitApp
    }
    msg(wParam, lParam, msg, hwnd) {    ; embedded OnMessage method, used with Gui "event sink"
        this["MyEdit2"].Value := "Mouse Move / wParam: " wParam "`r`nlParam: " lParam " / hwnd: " hwnd
    }
    Call(wParam, lParam, msg, hwnd) {   ; Custom user function when func obj is needed.
        state := (wParam) ? "Down" : "Up"
        this["MyEdit3"].Value := "LB " state " / wParam: " wParam "`r`nlParam: " lParam " / hwnd: " hwnd
    }
    __Get(key,p) {
        return this[key]
    }
}

class GuiCtl {
    __New(ctl) {
        this.GuiCtlObj := ctl
    }
    __Call(key,p) {
        If key = "OnCommand"
            this.GuiCtlObj.OnCommand(p[1],p[2],p.Has(3)?p[3]:1) ; <---------------------- Apparently all the original methods will have to be
        return                                                  ;                         wrapped, but then additional methods can be created.
    }
}

g := gui2.New(,"Test Gui")
OnMessage(0x200,g.msg) ; WM_MOUSEMOVE
OnMessage(0x201,g) ; WM_LBUTTONDOWN
OnMessage(0x202,g) ; WM_LBUTTONUP
g.OnEvent("close","gui_close")

ctl := g.Add("Edit","vMyEdit1 w200 r4 Section").OnCommand(0x300,"some_event") ; same as "Change" for OnEvent ; EN_CHANGE
ctl := g.Add("Edit","vMyEdit2 ReadOnly w200 r2")

g.Add("Edit","vMyEdit3 ReadOnly w200 r2")
g.Add("Edit","vMyEdit4 ReadOnly w200 h100")

g.Show()

F2:: {
    txt := "test method:`t" g.test() "`r`n"
    txt .= "test property:`t" g.test2 "`r`n"
    txt .= "super class name:`t" g.test3
    msgbox txt
}

EDIT: In general, one would have to access controls by: gui_obj.%ctlName%.%methods%(). Finding some limitations with this, and trying to work around them, ie. .GetPos().
User avatar
hoppfrosch
Posts: 418
Joined: 07 Oct 2013, 04:05
GitHub: hoppfrosch
Location: Rhine-Maine-Area, Hesse, Germany
Contact:

Re: AHK v2 GUI SubClass-ing example (and any other classes?)

Post by hoppfrosch » 26 Jan 2021, 02:08

What I don't understand is: What happens here?

Code: Select all

OnMessage(0x201,g) ; WM_LBUTTONDOWN
As documentation says, the second argument is a "functions name or a functions object".

As fas as I understand "g" is a class instance in your example ...
What is expected to happen/to be called if the message WM_LBUTTONDOWN is fired?
User avatar
TheArkive
Posts: 435
Joined: 05 Aug 2016, 08:06
GitHub: TheArkive

Re: AHK v2 GUI SubClass-ing example (and any other classes?)

Post by TheArkive » 26 Jan 2021, 05:12

@hoppfrosch

It turns out, you can have a class wrapper, and a single method named "Call". This method must have all the necessary parameters of a typical callback function for OnMessage().

So you can have a class with an entirely different purpose, but as long as you have a "Call" method (with appropriate params) then you can have that class double as a func object (in function, not in actual type).

I'm about to post a better working version of wrapping a GuiControl object. :D

EDIT: I finally understood this as I was figuring it out :P
User avatar
TheArkive
Posts: 435
Joined: 05 Aug 2016, 08:06
GitHub: TheArkive

Re: AHK v2 GUI SubClass-ing example (and any other classes?)

Post by TheArkive » 26 Jan 2021, 13:11

EDIT: Found a way to shorten the GuiControl wrapper even further. Still can't get much shorter. Without the defined properties[] (and trying to use __Get() and __Set() ) I end up getting circular references.

EDIT2: Managed to store some GuiControl properties (that don't change) in this.property. The rest need to be read from GuiControl object.

Just wanted to finish this train of thought. The only way I've found to extend GuiControls is to completely wrap them.

Code: Select all

Global g

g := gui2.New(,"Test Gui")
g.OnEvent("close","gui_close")

g.Add("Edit","vMyEdit1 w200 r4 Section").OnCommand(0x300,"some_event") ; same as "Change" for OnEvent ; EN_CHANGE
g.Add("Edit","vMyEdit2 ReadOnly w200 r2")
g.Add("Edit","vMyEdit3 ReadOnly w200 r2")
g.Add("Edit","vMyEdit4 ReadOnly w200 h100")
g.Add("Button","vBtn1 w100","Test1").OnEvent("click","ctl_events")
g.Add("Button","vBtn2 x+0 w100","Test2").OnEvent("click","ctl_events")
g.Add("Button","vBtn3 x+10 w100","Adj Col").OnEvent("click","ctl_events")

ctl := g.Add("ListView","vLV x220 ym w200",["Stuff"])
ctl.Add(,"Row1"), ctl.Add(,"Row2"), ctl.Add(,"Row3")

g.Show()

return

class gui2 extends gui2._gui_important_stuff {
    test2 := "property added"
    
    ; put user props / methods for GUI here
    
    test() {                            ; pointless method, just to illustrate adding custom methods.
        return "method added"           ; Use "super" or "this" to access the original GUI object and it's methods/properties.
    }
    some_event(ctl) {                   ; Custom user function for WM_COMMAND notifications, used with Gui "event sink" (EventObj)
        this["MyEdit4"].Value := "Title: " ctl.Name "`r`n`r`n" ctl.value 
    }
    gui_close(*) {                      ; Custom user function for close event; used with Gui "event sink"
        ExitApp
    }
    msg(wParam, lParam, msg, hwnd) {    ; Custom user function for OnMessage, used with Gui "event sink"
        this["MyEdit2"].Value := "Mouse Move / wParam: " wParam "`r`nlParam: " lParam " / hwnd: " hwnd
        return
    }
    Call(wParam, lParam, msg, hwnd) {   ; Custom user function when func obj is needed... OnMessage() in this case.
        state := (wParam) ? "Down" : "Up"
        this["MyEdit3"].Value := "LB " state " / wParam: " wParam "`r`nlParam: " lParam " / hwnd: " hwnd
    }
    ctl_events(ctl, p*) {               ; Custom user function for GUI control events when using "event sink"
        ctl := GuiCtl.New(ctl)
        If ctl.Name = "Btn1"
            msgbox "test method:`t" g.test() "`r`ntest property:`t" g.test2 "`r`ntest property:`t" g.test3
        Else If ctl.Name = "Btn2" {
            If (g["MyEdit1"].Enabled) {
                g["MyEdit1"].Enabled := false
                
                pos := g["MyEdit4"].GetPos()
                g["MyEdit4"].Move(pos.x+20,,pos.w-20)
                g["MyEdit2"].SetFont("s10","Times New Roman")
                g["MyEdit4"].Value := "setting Value prop for: " g["MyEdit4"].Name
                g.Title := "Testing..."
            } Else {
                g["MyEdit1"].Enabled := true
                
                pos := g["MyEdit4"].GetPos()
                g["MyEdit4"].Move(pos.x-20,,pos.w+20)
                g["MyEdit2"].SetFont("s8","Verdana")
                g["MyEdit4"].Value := "setting Value prop for: " g["MyEdit4"].Name
                g.Title := "Test GUI"
            }
        } Else If ctl.Name = "Btn3" {
            ctl.gui["LV"].ModifyCol(1,100)
            If ctl.gui["LV"].GetCount()
                ctl.gui["LV"].Delete()
            Else {
                ctl.gui["LV"].Add(,"Row1")
                ctl.gui["LV"].Add(,"Row2")
                ctl.gui["LV"].Add(,"Row3")
                ctl.gui["LV"].ModifyCol(1,190)
            }
        }
    }
    
    
    
    class _gui_important_stuff extends gui { ; put the important stuff out of the way
        test3 := "another prop"
        
        __New(opt:="", title:="", EventObj:="") {
            super.__New(opt, title, this)           ; Specify the event sink for the GUI obj as this subclass.
                                                    ; Required when func obj is needed and using a user class.
            
            OnMessage(0x200,ObjBindMethod(this,"msg"))  ; WM_MOUSEMOVE ; binding these outside the class doesn't work too well
                                                        ; ... best to do this in the class somewhere
            
            OnMessage(0x201,this) ; WM_LBUTTONDOWN ; these can be bound outside the class, because they are using .Call() method.
            OnMessage(0x202,this) ; WM_LBUTTONUP
        }
        __Item[key] {
            get => GuiCtl.New(super[key]) ; should be ReadOnly
        }
        Add(type, options:="", value:="") {
            return GuiCtl.New(super.Add(type, options, value))
        }
    }
}

class GuiCtl extends GuiCtl.GuiControl {
    test := "test property"
    
    ; put other properties / methods here!
    
    class GuiControl {
        __New(ctl) {
            this.GuiCtlObj := ctl ; store the original control in this.GuiCtlObj
            this.Type := ctl.type, this.hwnd := ctl.hwnd, this.ClassNN := ctl.ClassNN
        }
        Enabled[] {
            get => this.GuiCtlObj.Enabled
            set => this.GuiCtlObj.Enabled := value
        }
        Focused[] {                         ; This value isn't static, need to pull it
            get => this.GuiCtlObj.Focused   ; from the original GuiControl object.
        }
        Gui[] {                         ; Don't want to make another copy of the GUI.
            get => this.GuiCtlObj.Gui   ; So keeping this property.
        }
        Name[] {
            get => this.GuiCtlObj.Name
            set => this.GuiCtlObj.Name := Value
        }
        Text[] {
            get => this.GuiCtlObj.Text
            set => this.GuiCtlObj.Text := Value
        }
        Value[] {
            get => this.GuiCtlObj.Value
            set => this.GuiCtlObj.Value := value
        }
        
        __Call(key,p) {
            If key = "__Init" { ; This is only for preventing an error when __Init fires.
                return          ; Pre-defined custom properties can still be place up top.
            } Else If key = "GetPos" {              ; ByRef treatment doesn't wrap well.
                this.GuiCtlObj.GetPos(x, y, w, h)   ; Switching back to returning an obj.
                return {x:x, y:y, w:w, h:h}
            } Else If key {
                return this.GuiCtlObj.%key%(p*)
            }
        }
    }
}

I think this wrapper looks pretty good considering. I tried using __Get() / __Set() but kept getting circular reference issues. This is the closest / shortest version I've come up with.

I had to handle .GetPos() differently. The ByRef nature of the vars fed in didn't play well with this kind of wrapper. Other than that __Call() is working nicely along with p*.

@kczx3 any idea how to shorten the GuiCtl wrapper at the bottom? The defined props seems to be the only way to read/write to the original GuiControl properties and avoid circular references (trying to use __Get and __Set).
lexikos
Posts: 7142
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: AHK v2 GUI SubClass-ing example (and any other classes?)

Post by lexikos » 31 Jan 2021, 00:39

If you want to add methods to all control's of a given class, there is a much easier, more direct method.

Code: Select all

g := Gui.new()
btn := g.Add("Button")
MsgBox btn.base.__Class  ; Gui.Button
btn.base.DefineMethod("Test", (c,*) => MsgBox(c.HWND))
btn.Test()  ; Shows btn's HWND.
g.Destroy()

g2 := Gui.new()
btn2 := g2.Add("Button",, "Hello")
btn2.Test()  ; Still works.
g2.Destroy()
If the Gui.Button class actually existed, the only difference is that you wouldn't have to create a control in order to get its prototype; you would use Gui.Button.Prototype instead of btn.base.
TheArkive wrote:
23 Jan 2021, 13:36
I read up on super in the docs, but this seems to apply more to user defined classes/subclasses.
Of course. super can only be used inside a class, and the only kind of class that you can write code inside of is a user-defined class. However, it does not matter what kind of class the superclass is, only that the method exists in a superclass. If you override a method and don't use super, only your implementation executes, not the original (superclass) one.
User avatar
TheArkive
Posts: 435
Joined: 05 Aug 2016, 08:06
GitHub: TheArkive

Re: AHK v2 GUI SubClass-ing example (and any other classes?)

Post by TheArkive » 31 Jan 2021, 03:42

Thanks @lexikos!

Thanks for the example on DefineMethod(). That will definitely be useful.

EDIT: I'm a doof. I didn't get the depth of your reply the first reading. Now I get it. Thanks again :)
lexikos
Posts: 7142
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: AHK v2 GUI SubClass-ing example (and any other classes?)

Post by lexikos » 16 Feb 2021, 05:17

v2.0-a124 adds the GUI control classes to the Gui class (e.g. Gui.Button).
User avatar
TheArkive
Posts: 435
Joined: 05 Aug 2016, 08:06
GitHub: TheArkive

Re: AHK v2 GUI SubClass-ing example (and any other classes?)

Post by TheArkive » 16 Feb 2021, 13:23

Very cool! Thanks @lexikos!

EDIT: Loving the Built-In classes section! That will be friggin awesome...
Post Reply

Return to “AutoHotkey v2 Help”