ListView Extending class - defining selection Topic is solved

Get help with using AutoHotkey (v2 or newer) and its commands and hotkeys
AHK_user
Posts: 515
Joined: 04 Dec 2015, 14:52
Location: Belgium

ListView Extending class - defining selection

Post by AHK_user » 14 May 2022, 07:16

I tried to add some easy to use properties to the listview.
For example LV.table returns the content, but also can be used to set a specific content.

LV.Selection_table also works, but I would prever that I could use LV.Selection.table to retrieve the selected content, but this seems to be failing.
Does anybody can give me tips how to fix this/ or a better shorter way to define this?

Code: Select all

#SingleInstance force

class ListView_Ext extends Gui.ListView {	; Technically no need to extend classes unless
    Static __New() {	; you are attaching new base on control creation.
        For prop in this.Prototype.OwnProps()
            super.Prototype.%prop% := this.Prototype.%prop%
        super.Prototype.DefineProp("hwndHeader", { get: (this) => (hwndHeader := SendMessage(LVM_GETHEADER := 0x101F, 0, 0, , "ahk_id " . This.hwnd), (hwndHeader = "FAIL" ? False : hwndHeader)) })
        super.Prototype.DefineProp("columns", { get: (this) => (this.GetCount("Col"))})
        super.Prototype.DefineProp("rows", { get: (this) => (this.GetCount(""))})
        super.Prototype.DefineProp("table", { get: (this) => (this.GetTable(this)), Set: (this, value) => (this.SetTable(value))})
        super.Prototype.DefineProp("headerArray", { get: (this) => (this.GetHeaderArray()), Set: (this, value) => (this.SetHeaderArray(value))})

        super.Prototype.DefineProp("selection_table", { get: (this) => (this.GetTable("selected"))})
        super.Prototype.DefineProp("selection_rows", { get: (this) => (this.GetCount("S"))})
        super.Prototype.DefineProp("selection_row", { get: (this) => (this.GetNext()) })

        ; super.Prototype.Selection := Object()
        ; super.Prototype.Selection.parent := this
        ; super.Prototype.Selection.DefineProp("table", { get: (this) => (this.parent.GetTable("selected"))})
        ; super.Prototype.Selection.DefineProp("rows", { get: (this) => (this.GetCount("S"))})
    }

    Checked(row) => (SendMessage(4140, row - 1, 0xF000, , "ahk_id " this.hwnd) >> 12) - 1	; VM_GETITEMSTATE = 4140 / LVIS_STATEIMAGEMASK = 0xF000

    IconIndex(row, col := 1) {	; from "just me" LV_EX ; Link: https://www.autohotkey.com/boards/viewtopic.php?f=76&t=69262&p=298308#p299057
        LVITEM := Buffer((A_PtrSize = 8) ? 56 : 40, 0)	; create variable/structure
        NumPut("UInt", 0x2, "Int", row - 1, "Int", col - 1, LVITEM.ptr, 0)	; LVIF_IMAGE := 0x2 / iItem (row) / column num
        NumPut("Int", 0, LVITEM.ptr, (A_PtrSize = 8) ? 36 : 28)	; iImage
        SendMessage(StrLen(Chr(0xFFFF)) ? 0x104B : 0x1005, 0, LVITEM.ptr, , "ahk_id " this.hwnd)	; LVM_GETITEMA/W := 0x1005 / 0x104B
        return NumGet(LVITEM.ptr, (A_PtrSize = 8) ? 36 : 28, "Int") + 1	;iImage
    }

    GetColWidth(n) => (SendMessage(0x101D, n - 1, 0, this.hwnd))

    GetTable(Options := "") {
        Result := ""
        Loop this.GetCount("Column") {
            Result .= (A_Index = 1) ? "" : "`t"
            Result .= this.GetText(0, A_Index)
        }
        Result .= "`n" ListViewGetContent(Options, this)
        return Result
    }

    SetTable(Table) {
        this.Opt("-Redraw")

        ; Clear all columns
        this.Delete()
        Loop this.GetCount("Column") {
            this.DeleteCol(1)
        }

        loop parse Table, "`n", "`r" {
            aRow := StrSplit(A_LoopField, "`t")
            if (A_index = 1) {
                for Index, Header in aRow {
                    this.InsertCol(Index, , Header)
                }
            } else {
                this.Add("", aRow*)
            }
        }

        this.ModifyCol
        this.Opt("+Redraw")
        return
    }

    GetHeaderArray() {
        Result := ""
        Loop This.GetCount("Column") {
            Result .= (A_Index = 1) ? "" : "`t"
            Result .= This.GetText(0, A_Index)
        }
        return Result
    }
    SetHeaderArray(aHeaders) {
        this.Opt("-Redraw")
        for index, HeaderText in aHeaders {
            this.ModifyCol(Index,,HeaderText)
        }
        this.Opt("+Redraw")
    }
}

; Create the window:
MyGui := Gui()

; Create the ListView with two columns, Name and Size:
LV := MyGui.Add("ListView", "r20 w700", ["Name","Size"])

; Gather a list of file names from a folder and put them into the ListView:
Loop Files, A_MyDocuments "\*.*"
    LV.Add("select", A_LoopFileName, A_LoopFileSizeKB)

LV.ModifyCol  ; Auto-size each column to fit its contents.

; Display the window:
MyGui.Show

; sleep 5000
lv.headerArray := ["test","test2"]
MsgBox(lv.selection.table)

Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: ListView Extending class - defining selection  Topic is solved

Post by Helgef » 14 May 2022, 13:08

super.Prototype.Selection.parent := this, here this is ListView_Ext, it has no method GetTable. But even if it did, you want to pass the instance lv in lv.selection.table to the method GetTable, not ListView_Ext.

When you do lv.selection.x there is no way to know x when lv.selection is evaluated. So whatever lv.selection returns must handle all x you need, and in this case, x also needs to know about lv. For example,

Code: Select all

; in static __new
super.Prototype.defineprop 'selection', { get : sel_get }
	
sel_get(this) {
	sel := {}
	sel.DefineProp("table", { get: (self) => (this.GetTable("selected"))})  
	sel.DefineProp("rows", { get: (self) => (this.GetCount("S"))})
	return sel
}
This is for educational purposes, I do not think this approach is advisable
Cheers

edit, simplified example

AHK_user
Posts: 515
Joined: 04 Dec 2015, 14:52
Location: Belgium

Re: ListView Extending class - defining selection

Post by AHK_user » 20 May 2022, 13:42

@Helgef: Thanks this seems to be working quite well.

You mentioned that this is not advised, are you referring to that it is better to not to change the standard ahk gui object, but create a new class, or do you recommend to use a subclass for the selection.

Here is my current code.
I also added a cool feature that lets you loop over the listview, then it returns the rows one by one and the content of the rows.

It is working already great, but it is missing a lot of functionalities like looping over the selection, creating groups,..

lv.table => get or set the content of the listview as a table (tab and linefeeds)
lv.selection.table => returns the selection of the listview as a table (tab and linefeeds)
lv.selection.rows => returns the selection of the listview as a table (tab and linefeeds)
lv.rows => returns the number of rows
lv.colunms=> returns the number of colunms
lv.Checked(row) => returns the checked status
lv.headerArray := ["test","test2"] => quickly set or get the headers
lv.SetExplorerTheme() => change the theme
lv.GetObject(Options := "", aHeaders:="",includeIcons:=0) => retrieve the content as an array of objects (aHeaders can be used to change the properties names, as no spaces are allowed. set IncludeIcons to 1 to also include the iconnumber and checked status.
lv.GetObject(aRows, aProps := "") => set the content of the listview as an object

for row, rowcontent in lv => loop over the content :D :D :D I really like this one

lv.Filter(SearchString, colNumber:="") => quickly filter the list

Code: Select all

#SingleInstance force
#Include "C:\Users\Dimitri\Documents\Autohotkey v2\Projects\Gui\V2GuiCreator\Lib\ObjectGui.ah2"

class ListView_Ext extends Gui.ListView {	; Technically no need to extend classes unless
    Static __New() {	; you are attaching new base on control creation.
        For prop in this.Prototype.OwnProps()
            super.Prototype.%prop% := this.Prototype.%prop%
        super.Prototype.DefineProp("hwndHeader", { get: (this) => (hwndHeader := SendMessage(LVM_GETHEADER := 0x101F, 0, 0, , "ahk_id " . This.hwnd), (hwndHeader = "FAIL" ? False : hwndHeader)) })
        super.Prototype.DefineProp("columns", { get: (this) => (this.GetCount("Col"))})
        super.Prototype.DefineProp("rows", { get: (this) => (this.GetCount(""))})
        super.Prototype.DefineProp("table", { get: (this) => (this.GetTable(this)), Set: (this, value) => (this.SetTable(value))})
        super.Prototype.DefineProp("headerArray", { get: (this) => (this.GetHeaderArray()), Set: (this, value) => (this.SetHeaderArray(value))})

        super.Prototype.defineprop("selection", { get: sel_get } )
        sel_get(this) {
            sel := {}
            sel.DefineProp("table", { get: (sel) => (this.GetTable("selected")) })
            sel.DefineProp("rows", { get: (sel) => (this.GetCount("S")) })
            sel.DefineProp("row", { get: (sel) => (this.GetNext()) })
            return sel
        } 
    }
    
    Checked(rowNumber) => (SendMessage(VM_GETITEMSTATE := 4140, rowNumber - 1, 0xF000, , "ahk_id " this.hwnd) >> 12) - 1	; VM_GETITEMSTATE = 4140 / LVIS_STATEIMAGEMASK = 0xF000

    GetIconIndex(rowNumber, col := 1) {	; from "just me" LV_EX ; Link: https://www.autohotkey.com/boards/viewtopic.php?f=76&t=69262&p=298308#p299057
        LVITEM := Buffer((A_PtrSize = 8) ? 56 : 40, 0)	; create variable/structure
        NumPut("UInt", 0x2, "Int", rowNumber - 1, "Int", col - 1, LVITEM.ptr, 0)	; LVIF_IMAGE := 0x2 / iItem (row) / column num
        NumPut("Int", 0, LVITEM.ptr, (A_PtrSize = 8) ? 36 : 28)	; iImage
        SendMessage(StrLen(Chr(0xFFFF)) ? 0x104B : 0x1005, 0, LVITEM.ptr, , "ahk_id " this.hwnd)	; LVM_GETITEMA/W := 0x1005 / 0x104B
        return NumGet(LVITEM.ptr, (A_PtrSize = 8) ? 36 : 28, "Int") + 1	;iImage
    }

    GetColWidth(colNumber) => (SendMessage(LVM_GETCOLUMNWIDTH := 0x101D, colNumber - 1, 0, this.hwnd))

    GetTable(Options := "") {
        Result := ""
        Loop this.GetCount("Column") {
            Result .= (A_Index = 1) ? "" : "`t"
            Result .= this.GetText(0, A_Index)
        }
        Result .= "`n" ListViewGetContent(Options, this)
        return Result
    }

    SetTable(Table,headerIncl:=true) {
        this.Opt("-Redraw")

        ; Remove content
        this.Delete()

        if (headerIncl){
            ; Clear all columns
            Loop this.GetCount("Column") {
                this.DeleteCol(1)
            }
        }
        
        loop parse Table, "`n", "`r" {
            aRow := StrSplit(A_LoopField, "`t")
            if (A_index = 1 and headerIncl) {
                for Index, Header in aRow {
                    this.InsertCol(Index, , Header)
                }
            } else {
                this.Add("", aRow*)
            }
        }

        this.ModifyCol
        this.Opt("+Redraw")
        return
    }

    GetHeaderArray() {
        Result := ""
        Loop This.GetCount("Column") {
            Result .= (A_Index = 1) ? "" : "`t"
            Result .= This.GetText(0, A_Index)
        }
        return Result
    }

    SetHeaderArray(aHeaders) {
        this.Opt("-Redraw")
        for index, HeaderText in aHeaders {
            this.ModifyCol(Index,,HeaderText)
        }
        this.Opt("+Redraw")
    }

    GetObject(Options := "", aHeaders:="",includeIcons:=0) {
        aResult := Array()
        if (aHeaders=""){
            aHeaders := Array()
            Loop this.GetCount("Column") {
                aHeaders.Push(StrReplace(this.GetText(0, A_Index), " ", "_"))
            }
        }
        Loop parse, ListViewGetContent(Options, lv), "`n", "`r" {
            oRow := Object()
            Loop parse, A_LoopField, "`t" {
                ; oRow.DefineProp(aHeaders[A_index], { get: ((result, this) => (result)).bind(A_LoopField) })
                propertyName := aHeaders[A_index]
                oRow.%propertyName% := A_LoopField
            }
            aResult.Push(oRow)
        }
        if (includeIcons){
            rowNumber := 0
            loop aResult.Length {
                if (InStr(Options,"Selected")){
                    rowNumber := LV.GetNext(rowNumber)
                } else{
                    rowNumber++
                }
                aResult[A_Index].IconIndex := this.GetIconIndex(rowNumber)
                aResult[A_Index].Checked := this.Checked(rowNumber)
            }
        }
        return aResult
    }

    ; aRows := An array of objects with the data
    ; aProps := An array of the properties:
    ;           - an array of strings => uses the strings as properties
    ;           - or an array of objects with properties name and text => uses text as header and name as properties
    ;           - empty => uses all the properties of the first object of aRows
    SetObject(aRows, aProps := "") {
        this.Opt("-Redraw")
        this.Delete()
        Loop this.GetCount("Column") {
            this.DeleteCol(1)
        }
        aHeaders := Type(aProps)="Array" ? aProps : aRows[1].OwnProps()
        if (aProps = ""){
            aHeaders := Array()
            For PropName, PropValue in aRows[1].OwnProps() {
                if (PropName="IconNumber" or PropName="Checked"){
                    continue
                }
                this.InsertCol(A_Index, , PropName)
                aHeaders.Push(PropName)
            }
        }
        else if (aHeaders[1].HasProp("name")) { ; an array of objects with the name and text property
            aHeaders2 := Array()
            For index, Property in aHeaders {
                this.InsertCol(A_Index, , Property.HasProp("text")? Property.text : Property.name)
                aHeaders2.Push(Property.name)
            }
            aHeaders := aHeaders2
        } else { ; an array of strings
            For index, Property in aHeaders {
                this.InsertCol(A_Index, , Property)
            }
        }

        for Index, oRow in aRows
        {
            aRow := Array()
            For index, Property in aHeaders {
                aRow.Push(oRow.%Property%)
            }
            this.Add((oRow.HasProp("Iconnumber") ? "Icon" oRow.IconNumber : "") . ((oRow.HasProp("Checked") and oRow.Checked=1) ? " Check"  : ""), aRow*)
        }

        this.ModifyCol
        this.Opt("+Redraw")
        return
    }

    __Enum(NumberOfVars){
        ; Lexical scope bound.
        ; Serves as an index counter.
        i := 1
        
        aContent := StrSplit(ListViewGetContent(, this),"`n","`r")
        imax := aContent.Length

        EnumerateElements(&element){
            ; Stop enumerating when there are no more elements
            ; left in the array.
            if (i > imax)
                return false

            ; Assign to the 'for' loop and decrement.
            element := aContent[i]
            i += 1

            ; Continue enumeration.
            return true
        }

        EnumerateIndexWithElements(&index, &element){
            if (i > imax)
                return false

            index := i
            element := aContent[i]
            i += 1

            return true
        }

        ; Decide, based on the 'for' loop type, which
        ; enumeration function is the appropriate one.
        return (NumberOfVars = 1)
            ? EnumerateElements
            : EnumerateIndexWithElements
    }

    Filter(SearchString, colNumber:=""){
        TableNew := ""
        this.Opt("-Redraw")
        Index := 0
        Loop parse, this.table, "`n","`r"
        {
            Haystack := colNumber = "" ? A_LoopField : StrSplit(A_LoopField,"`t")[colNumber]
            if (A_index!=1) and !Instr(A_LoopField,SearchString){
                This.Delete(Index)
                Index--
            }
            Index++
        }
        this.Opt("+Redraw")
    }

    SetExplorerTheme(){
        if (DllCall("kernel32\GetVersion", "uchar") > 5) {
            VarSetStrCapacity(&ClassName, 1024)
            if (DllCall("user32\GetClassName", "ptr", this.hwnd, "str", ClassName, "int", 512, "int")) {
                return !DllCall("uxtheme\SetWindowTheme", "ptr", this.hwnd, "str", "Explorer", "ptr", 0)
            }
        }
        return false
    }
    
}

; Create the window:
MyGui := Gui()

; Create the ListView with two columns, Name and Size:
LV := MyGui.Add("ListView", "checked r20 w700", ["Name","Size"])

ImageListID := IL_Create(10)	; Create an ImageList to hold 10 small icons.
LV.SetImageList(ImageListID)	; Assign the above ImageList to the current ListView.
Loop 20	; Load the ImageList with a series of icons from the DLL.
    IL_Add(ImageListID, "shell32.dll", A_Index)

; Gather a list of file names from a folder and put them into the ListView:
Loop Files, A_MyDocuments "\*.*"
    LV.Add("Icon" . A_Index, A_LoopFileName, A_LoopFileSizeKB)

LV.ModifyCol  ; Auto-size each column to fit its contents.

; Display the window:
MyGui.Show


aRows := [{ test: "eeee", name: "www", iconnumber:2, checked:1 }, { test: "ddd", name: "eeeee" }]

lv.SetObject(aRows,[{ name: "test", text: "tast" },{ name: "name", text: "Name" }])
ObjectGui(LV.GetObject(, , 1))
ObjectGui(aRows)
; lv.Filter("a")

; for index, rowvalue in lv{
;     MsgBox(index " " rowvalue)
; }

sleep 5000
; lv.headerArray := ["test","test2"]
MsgBox(lv.selection.table)
MsgBox(lv.rows)

LV.SetExplorerTheme()

table := "test`tvalue`ndddddddddddd`tG`ntest2`t4564"
; LV.table := table
aRows := [{test:"ssfsddf",name:"sdfsd"},{ test: "ffff", name: "ssssss" }]
lv.SetObject(aRows)

Last edited by AHK_user on 21 May 2022, 08:13, edited 1 time in total.

Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: ListView Extending class - defining selection

Post by Helgef » 21 May 2022, 03:28

I thought it was a bit convoluted, your first approach with selection_table was fine imo. I suppose it comes down to preference. I will look at your example later, if I remember :P

Cheers.

User avatar
andymbody
Posts: 916
Joined: 02 Jul 2017, 23:47

Re: ListView Extending class - defining selection

Post by andymbody » 25 Oct 2023, 08:08

Thx for this

Post Reply

Return to “Ask for Help (v2)”