TreeList Control (Experimental)

Post your working scripts, libraries and tools for AHK v1.1 and older
User avatar
Alguimist
Posts: 428
Joined: 05 Oct 2015, 16:41
Contact:

TreeList Control (Experimental)

24 Dec 2017, 21:25

Hidden in rasdlg.dll, there is a control class called "TreeList". This control reproduces the features of a TreeView in a ListView. A TreeView with multiple columns.

I was searching for a way to create a control similar to that of Process Explorer when I found a Google Groups page that gives some details on how to create a TreeList and its undocumented messages. Then I created a small library for it.

TreeList Class:

Code: Select all

; TreeList Control Class (v1.1.1)

Class TreeList {
    Static TLM_INSERTITEM := 0x401
    , TLM_DELETEITEM := 0x402
    , TLM_DELETEALLITEMS := 0x403
    , TLM_GETITEM := 0x404
    , TLM_SETITEM := 0x405
    , TLM_GETITEMCOUNT := 0x406
    , TLM_GETNEXTITEM := 0x407
    , TLM_EXPAND := 0x408
    , TLM_SETIMAGELIST := 0x409
    , TLM_GETIMAGELIST := 0x40A
    , TLM_INSERTCOLUMN := 0x40B
    , TLM_DELETECOLUMN := 0x40C
    , TLM_SELECTITEM := 0x40D
    , TLM_REDRAWWINDOW := 0x40E
    , TLM_ISEXPANDED := 0x40F
    , TLM_GETCOLUMNWIDTH := 0x410
    , TLM_SETCOLUMNWIDTH := 0x411

    __New(hWndParent := 0, X := 0, Y := 0, Width := 300, Height := 200, Style := 0x5001000D, ExStyle := 0) {
        hMod := DllCall("LoadLibrary", "Str", "rasdlg.dll", "Ptr")
        If (!hMod) {
            MsgBox 0x10, Error, Failed to load library rasdlg.dll.
            Return
        }

        this.hWnd := DllCall("CreateWindowEx"
                     , "Uint" , ExStyle
                     , "Str"  , "TreeList"
                     , "Str"  , ""
                     , "UInt" , Style
                     , "Int"  , X
                     , "Int"  , Y
                     , "Int"  , Width
                     , "Int"  , Height
                     , "Ptr" , hWndParent
                     , "UInt" , 0
                     , "Ptr" , hMod
                     , "UInt" , 0, "Ptr")

        If (!this.hWnd) {
            MsgBox 0x10, Error, Failed to create the TreeList control.
            Return
        }

        Ptr := A_PtrSize == 8 ? "Ptr" : ""
        this.OldWndProc := DllCall("GetWindowLong" . Ptr, "Ptr", this.hWnd, "Int", -4, "Ptr") ; GWL_WNDPROC
        _TreeListHandler(this.OldWndProc, -1, 0, 0)
        this.NewWndProc := RegisterCallback("_TreeListHandler", "", 4)
        this.OldWndProc := DllCall("SetWindowLong" . Ptr, "Ptr", this.hWnd, "Int", -4, "Ptr", this.NewWndProc, "Ptr")

        this.hLV  := this.GetListView()
        this.hHdr := this.GetHeader()
        this.hMod := hMod

        WinSet Style, +0x800000, % "ahk_id" this.hLV ; WS_BORDER
    }

    AddColumn(Text, Width := 100, Align := "", Pos := -1) {
        Static fmt := {"Left": 0, "Center": 2, "Right": 1}

        VarSetCapacity(LVCOLUMN, A_PtrSize == 8 ? 56 : 44, 0)

        Mask := 0x6 ; (LVCF_TEXT := 0x4, LVCF_WIDTH := 0x2)
        If (fmt[Align] != "") {
            Mask |= 0x1 ; LVCF_FMT
            NumPut(fmt[Align], LVCOLUMN, 4, "Int") ; fmt
        }
        pszText := A_IsUnicode ? &Text : This.WStr(Text, WText)
        NumPut(Mask, LVCOLUMN, 0, "UInt") ; mask
        NumPut(Width, LVCOLUMN, 8, "Int") ; cx
        NumPut(pszText, LVCOLUMN, A_PtrSize == 8 ? 16 : 12, "Ptr") ; pszText
        NumPut(iImage := 1, LVCOLUMN, A_PtrSize == 8 ? 32 : 24, "Int")

        If (Pos == -1) {
            Pos := this.GetColumnCount()
        }

        SendMessage, % this.TLM_INSERTCOLUMN, %Pos%, % &LVCOLUMN,, % "ahk_id" this.hWnd
    }

    SetColumnText(ColN, NewText) {
        VarSetCapacity(LVCOLUMN, A_PtrSize == 8 ? 56 : 44, 0)
        NumPut(0x4, LVCOLUMN, 0, "UInt") ; mask (LVCF_TEXT)
        NumPut(&NewText, LVCOLUMN, A_PtrSize == 8 ? 16 : 12, "Ptr") ; pszText
        SendMessage % A_IsUnicode ? 0x1060 : 0x101A, ColN - 1, &LVCOLUMN,, % "ahk_id" . this.hLV ; LVM_SETCOLUMN
        Return ErrorLevel
    }

    DeleteColumn(ColN) {
        SendMessage % this.TLM_DELETECOLUMN, ColN - 1,,, % "ahk_id" . this.hWnd
        Return ErrorLevel
    }

    Add(ParentID := 0, Icon := "", Fields*) {
        Static TVI_LAST := -65534 & 0xFFFFFFFF 

        Mask := Icon != "" ? 0x3 : 0x1 ; LVIF_TEXT = 1, LVIF_IMAGE = 0x2
        Text := Fields[1]
        pszText := A_IsUnicode ? &Text : This.WStr(Text, WText)
        VarSetCapacity(LVITEM, A_PtrSize == 8 ? 88 : 60, 0)
        NumPut(Mask, LVITEM, 0, "UInt") ; mask
        NumPut(0, LVITEM, 4, "Int") ; iItem
        NumPut(pszText, LVITEM, A_PtrSize == 8 ? 24 : 20, "Ptr") ; pszText
        If (Icon != "") {
            NumPut(Icon - 1, LVITEM, A_PtrSize == 8 ? 36 : 28, "Int") ; iImage
        }

        ; TL_INSERTSTRUCT
        VarSetCapacity(TVINSERTSTRUCT, A_PtrSize == 8 ? 104 : 68, 0)
        NumPut(ParentID, TVINSERTSTRUCT, 0, "Ptr") ; hParent
        NumPut(TVI_LAST, TVINSERTSTRUCT, A_PtrSize == 8 ? 8 : 4, "Ptr") ; hInsertAfter
        NumPut(&LVITEM, TVINSERTSTRUCT, A_PtrSize == 8 ? 16 : 8, "Ptr")

        iItem := this.Send(this.TLM_INSERTITEM, 0, &TVINSERTSTRUCT)

        ; Sub items
        For Each, Field in Fields {
            If (A_Index == 1) {
                Continue
            }
            pszText := A_IsUnicode ? &Field : This.WStr(Field, WText)
            NumPut(iItem, LVITEM, 4, "Int") ; iItem
            NumPut(A_Index - 1, LVITEM, 8, "Int") ; iSubItem
            NumPut(pszText, LVITEM, A_PtrSize == 8 ? 24 : 20, "Ptr") ; pszText
            this.Send(this.TLM_SETITEM, 0, &LVITEM)
        }

        Return iItem
    }

    Delete(ItemID := "") {
        If (ItemID == "") {
            Return this.Send(this.TLM_DELETEALLITEMS, 0, 0)
        } Else {
            Return this.Send(this.TLM_DELETEITEM, 0, ItemID)
        }
    }

    Select(ItemID) {
        Return this.Send(this.TLM_SELECTITEM, 9, ItemID) ; TLGN_CARET
    }

    Expand(ItemID) {
        Return this.Send(this.TLM_EXPAND, 1, ItemID) ; 0 = toggle
    }

    IsExpanded(ItemID) {
        Return this.Send(this.TLM_ISEXPANDED, 0, ItemID)
    }

    Collapse(ItemID) {
        Return this.Send(this.TLM_EXPAND, 2, ItemID)
    }

    SetImageList(ImageListID) {
        Return this.Send(this.TLM_SETIMAGELIST, 0, ImageListID)
    }

    GetImageList() {
        Return this.Send(this.TLM_GETIMAGELIST, 0, 0)
    }

    GetCount() {
        Return this.Send(this.TLM_GETITEMCOUNT, 0, 0)
    }

    GetColumnCount() {
        SendMessage 0x1200, 0, 0,, % "ahk_id" . this.hHdr ; HDM_GETITEMCOUNT
        Return ErrorLevel
    }

    GetColumnWidth(ColN) {
        Return this.Send(this.TLM_GETCOLUMNWIDTH, ColN - 1, 0)
    }

    ; LVSCW_AUTOSIZE (-1) Automatically sizes the column.
    ; LVSCW_AUTOSIZE_USEHEADER (-2): Automatically sizes the column to fit the header text.
    ; If you use this value with the last column, its width is set to fill the remaining width of the list-view control.
    SetColumnWidth(ColN, Width := -1) {
        Static AutoSize := {"Auto": -1, "AutoHdr": -2}
        If (AutoSize[Width] != "") {
            Width := AutoSize[Width]
        }
        Return this.Send(this.TLM_SETCOLUMNWIDTH, ColN - 1, Width)
    }

    GetListView() {
        Return DllCall("GetWindow", "Ptr", this.hWnd, "UInt", 5) ; GW_CHILD
    }

    GetHeader() {
        SendMessage 0x101F, 0, 0,, % "ahk_id" . this.hLV ; LVM_GETHEADER
        Return ErrorLevel
    }

    GetSelection() {
        Return this.Send(this.TLM_GETNEXTITEM, 32, 0)
    }

    GetRoot() {
        Return this.Send(this.TLM_GETNEXTITEM, 0, 0) ; TLGN_ROOT
    }

    GetChild(ParentItemID) {
        Return this.Send(this.TLM_GETNEXTITEM, 2, ParentItemID) ; TLGN_CHILD
    }

    GetNext(ItemID := "") {
        If (ItemID == "") {
            Return this.GetRoot()
        } Else {
            Return this.Send(this.TLM_GETNEXTITEM, 4, ItemID) ; TLGN_NEXT
        }
    }

    GetPrev(ItemID) {
        Return this.Send(this.TLM_GETNEXTITEM, 8, ItemID) ; TLGN_PREVIOUS
    }

    GetParent(ItemID) {
        Return this.Send(this.TLM_GETNEXTITEM, 1, ItemID) ; TLGN_PARENT
    }

    SetText(ItemID, ColN, NewText) {
        pszText := A_IsUnicode ? &NewText : This.WStr(NewText, WText)
        VarSetCapacity(LVITEM, A_PtrSize == 8 ? 88 : 60, 0)
        NumPut(0x1, LVITEM, 0, "UInt") ; mask (TLIF_TEXT)
        NumPut(ItemID, LVITEM, 4, "Int") ; iItem
        NumPut(ColN - 1, LVITEM, 8, "Int") ; iSubItem
        NumPut(pszText, LVITEM, A_PtrSize == 8 ? 24 : 20, "Ptr") ; pszText

        Return this.Send(this.TLM_SETITEM, 0, &LVITEM)
    }

    GetText(ItemID, ColN := 1) {
        VarSetCapacity(LVITEM, A_PtrSize == 8 ? 88 : 60, 0)
        NumPut(0x1, LVITEM, 0, "UInt") ; mask (TLIF_TEXT)
        NumPut(ItemID, LVITEM, 4, "Int") ; iItem
        NumPut(ColN - 1, LVITEM, 8, "Int") ; iSubItem
        VarSetCapacity(pszText, 256, 0)
        NumPut(&pszText, LVITEM, A_PtrSize == 8 ? 24 : 20, "Ptr")
        NumPut(256, LVITEM, A_PtrSize == 8 ? 32 : 24, "Int") ; cchTextMax

        this.Send(this.TLM_GETITEM, 0, &LVITEM)
        Return StrGet(NumGet(LVITEM, A_PtrSize == 8 ? 24 : 20, "Ptr"), "UTF-16") ; pszText
    }

    SetIcon(ItemID, IconIndex) {
        VarSetCapacity(LVITEM, A_PtrSize == 8 ? 88 : 60, 0)
        NumPut(0x2, LVITEM, 0, "UInt") ; mask (TLIF_IMAGE)
        NumPut(ItemID, LVITEM, 4, "Int") ; iItem
        NumPut(IconIndex, LVITEM, A_PtrSize == 8 ? 36 : 28, "Int") ; iImage

        Return this.Send(this.TLM_SETITEM, 0, &LVITEM)
    }

    ; Get icon index in the image list
    GetIcon(ItemID) {
        VarSetCapacity(LVITEM, A_PtrSize == 8 ? 88 : 60, 0)
        NumPut(0x2, LVITEM, 0, "UInt") ; mask (TLIF_IMAGE)
        NumPut(ItemID, LVITEM, 4, "Int") ; iItem
        this.Send(this.TLM_GETITEM, 0, &LVITEM)
        Return NumGet(LVITEM, A_PtrSize == 8 ? 36 : 28, "Int") + 1
    }

    Redraw() {
        Return this.Send(this.TLM_REDRAWWINDOW, 0, 0)
    }

    SetEventHandler(EventHandler) {
        _TreeListStorage(this.hWnd, EventHandler)
    }

    Send(Msg, wParam, lParam) {
        SendMessage Msg, wParam, lParam,, % "ahk_id" . this.hWnd
        Return ErrorLevel
    }

    WStr(ByRef AStr, ByRef WStr) {
        Size := StrPut(AStr, "UTF-16")
        VarSetCapacity(WStr, Size * 2, 0)
        StrPut(ASTr, &WStr, "UTF-16")
        Return &Wstr
    }
}

_TreeListStorage(hWnd, Callback := "") {
    Static o := {}
    Return (o[hWnd] != "") ? o[hWnd] : o[hWnd] := Callback
}

_TreeListHandler(hWnd, msg, wParam, lParam) {
    Static n := {-2: "Click", -3: "DoubleClick", -5: "RightClick", -7: "SetFocus", -8: "KillFocus", -155: "KeyDown"}
    Static OldWndProc
    If (msg == -1) {
        OldWndProc := hWnd
    }

    If (msg == 78) { ; WM_NOTIFY (0x4E)
        hWndFrom := NumGet(lParam + 0)
        idFrom := NumGet(lParam + 4)
        Code := NumGet(lParam + 0, A_PtrSize * 2, "Int")

        If (Code > -7) { ; NM_CLICK, NM_DBLCLK, NM_RCLICK
            Row := NumGet(lParam + 0, A_PtrSize == 8 ? 24 : 12, "Int") + 1 ; NMITEMACTIVATE iItem
            Col := NumGet(lParam + 0, A_PtrSize == 8 ? 28 : 16, "Int") + 1 ; NMITEMACTIVATE iSubItem
        } Else If (Code == -155) { ; LVN_KEYDOWN
            Key := NumGet(lParam + 0, A_PtrSize == 8 ? 24 : 12, "Short") ; NMLVKEYDOWN wVKey (key code)
        }

        Handler := _TreeListStorage(hWnd)
        If (Handler != "") {
            Event := (n[Code] != "") ? n[Code] : Code
            %Handler%(hWnd, Event, Row, Col, Key)
        }
    }

    Return DllCall("CallWindowProcA", "Ptr", OldWndProc, "Ptr", hWnd, "UInt", msg, "Ptr", wParam, "Ptr", lParam)
}
TreeList Test:

Code: Select all

; TreeList Test

#NoEnv
#SingleInstance Force
SetBatchLines -1

#Include TreeList.ahk

Menu Tray, Icon, shell32.dll, 42

Gui +Resize +hWndhMainWnd
Gui Font, s9, Segoe UI
Gui Color, White

Gui Add, Button, x8 y8   w100 h23 gAddRoot, Add Root
Gui Add, Button, x8 y38  w100 h23 gAddChild, Add Child
Gui Add, Button, x8 y68  w100 h23 gGetText, Get Text
Gui Add, Button, x8 y98  w100 h23 gChangeItem, Change Item
Gui Add, Button, x8 y128 w100 h23 gSelectParent, Select Parent
Gui Add, Button, x8 y158 w100 h23 gSelectChild, Select Child
Gui Add, Button, x8 y188 w100 h23 gSelectNext, Select Next
Gui Add, Button, x8 y218 w100 h23 gExpand, Expand
Gui Add, Button, x8 y248 w100 h23 gCollapse, Collapse
Gui Add, Button, x8 y278 w100 h23 gToggleIcons, Toggle Icons
Gui Add, Button, x8 y308 w100 h23 gToggleGrid, Toggle Grid
Gui Add, Button, x8 y338 w100 h23 gDelete, Delete
Gui Add, Button, x8 y368 w100 h23 gDeleteAll, Delete All
Gui Add, Button, x8 y398 w100 h23 gReloadItems, Reload

; TreeList ImageList
Global ImageListID := IL_Create(12)
Loop 10 {
    IL_Add(ImageListID, "shell32.dll", A_Index)
}
IL_Add(ImageListID, "shell32.dll", 70)
IL_Add(ImageListID, "shell32.dll", 71)

; Create the TreeList
Global TL := New TreeList(hMainWnd, DPIScale(117), DPIScale(8), DPIScale(510), DPIScale(413))

TL.SetImageList(ImageListID)

; Add columns (mandatory)
TL.AddColumn("Column 1", DPIScale(289))
TL.AddColumn("Column 2", DPIScale(95))
TL.AddColumn("Column 3", DPIScale(123))

LoadItems()

TL.SetEventHandler("OnTreeList")

Gui Add, StatusBar
SB_SetParts(120, 80, 80)

Gui Show, w635 h453, TreeList Test
Return

GuiSize:
    If (A_EventInfo == 1) {
        Return
    }

    ControlMove,,,, % DPIScale(A_GuiWidth - 125), % DPIScale(A_GuiHeight - 40), % "ahk_id" . TL.hWnd
    ControlMove,,,, % DPIScale(A_GuiWidth - 125), % DPIScale(A_GuiHeight - 40), % "ahk_id" . TL.hLV
Return

GuiEscape:
GuiClose:
    Gui Destroy
    DllCall("FreeLibrary", "Ptr", TL.hMod)
    ExitApp

; Default items
LoadItems() {
    DateTime := GetDateTime()

    RootID1 := TL.Add(0, "4", "Files", "<DIR>", DateTime)
    TL.Add(RootID1, "12", "Commands.txt", "5 KB", DateTime)
    TL.Add(RootID1, "2", "Database.xml", "7 MB", DateTime)

    RootID2 := TL.Add(0, "4", "Settings", "<DIR>", DateTime)
    TL.Add(RootID2, "11", "Settings.ini", "1 KB", DateTime)
    TL.Add(RootID2, "2", "Config.xml", "2 KB", DateTime)

    TL.Expand(RootID1)
    TL.Expand(RootID2)
    TL.Select(RootID1)
}

GetDateTime() {
    FormatTime DateTime, D1
    DateTime := RegExReplace(DateTime, "(.*)\s(.*)", "$2 $1")
    Return DateTime
}

AddRoot() {
    Folder := GetRandomName()
    DateTime := GetDateTime()
    ItemID := TL.Add(0, "4", Folder, "<DIR>", DateTime)
    TL.Select(ItemID)
}

AddChild() {
    Filename := GetRandomName() . ".EXT"
    DateTime := GetDateTime()

    ParentID := TL.GetSelection()
    Size := TL.GetText(ParentID, 2)
    If (Size != "<DIR>") {
        TL.SetIcon(ParentID, 4)
        Name := TL.GetText(ParentID, 1)
        Name := SubStr(Name, 1, InStr(Name, ".") - 1)
        TL.SetText(ParentID, 1, Name)
        TL.SetText(ParentID, 2, "<DIR>")
    }

    ItemId := TL.Add(ParentID, "1", Filename, "1 KB", DateTime)
    TL.Expand(ParentID)
    TL.Select(ItemID)
}

GetText() {
    Col1 := TL.GetText(TL.GetSelection(), 1)
    Col2 := TL.GetText(TL.GetSelection(), 2)
    Col3 := TL.GetText(TL.GetSelection(), 3)

    Gui +OwnDialogs
    MsgBox 0x40, TreeList, % "Column 1:  " . Col1 . "`nColumn 2:  " . Col2 . "`nColumn 3:  " Col3
}

ChangeItem() {
    ItemID := TL.GetSelection()
    Size := TL.GetText(ItemID, 2)
    Name := GetRandomName()
    If (Size != "<DIR>") {
        Name .= ".EXT"
        TL.SetIcon(ItemID, 0)
    }

    TL.SetText(ItemID, 1, Name)
    TL.SetText(ItemID, 3, GetDateTime())
}

SelectParent() {
    TL.Select(TL.GetParent(TL.GetSelection()))
}

SelectChild() {
    TL.Select(TL.GetChild(TL.GetSelection()))
}

SelectNext() {
    TL.Select(TL.GetNext(TL.GetSelection()))
}

Expand() {
    TL.Expand(TL.GetSelection())
}

Collapse() {
    TL.Collapse(TL.GetSelection())
}

ToggleIcons() {
    If (TL.GetImageList()) {
        TL.SetImageList(0)
    } Else {
        TL.SetImageList(ImageListID)
    }
}

ToggleGrid() {
    SendMessage 0x1037,,,, % "ahk_id " . TL.hLV ; LVM_GETEXTENDEDLISTVIEWSTYLE
    ExtStyle := ErrorLevel & 1 ? 0 : 1
    SendMessage 0x1036, 1, %ExtStyle%,, % "ahk_id " . TL.hLV ; LVM_SETEXTENDEDLISTVIEWSTYLE
}

Delete() {
    TL.Delete(TL.GetSelection())
}

DeleteAll() {
    TL.Delete()
}

ReloadItems() {
    DeleteAll()
    DeleteAll() ; ?
    LoadItems()
}

GetRandomName() {
    String := ""
    Loop 8 {
        Random Num, 65, 90
        String .= Chr(Num)
    }
    Return String
}

OnTreeList(hWnd, Event, Row, Col, Key) {
    If (Event == "Click") {
        SB_SetText(" ID: " . TL.GetSelection())
        SB_SetText("Row: " . Row, 2)
        SB_SetText("Column: " . Col, 3)
    }
    Else If (Event == "RightClick") {
        Menu ContextMenu, Add, Get Text, GetText
        Menu ContextMenu, Show
    }
    Else If (Event == "DoubleClick") {
        GetText()
    }
}

DPIScale(x) {
    Return (x * A_ScreenDPI) // 96
}
Screenshot:
Image

Limitations:
- The script crashes when executed with AHK 64-bit. Fixed.
- TLM_DELETECOLUMN hides the items of the next column.
- Column header drag'n drop and sorting are not available.
- On Windows 10, the remainder of the column header is a black rectangle.
- LVS_EX_CHECKBOXES is not supported.
- ListView features introduced in Windows Vista are not supported (group view, Explorer theme).

Alternative:
- DebugVars has a TreeListView class with in-cell editing.

Credits to:
- just me for x64 and ANSI compatibility.
- HotKeyIt for converting the code to AHK v2 and some corrections.
Last edited by Alguimist on 29 Dec 2017, 23:58, edited 7 times in total.
User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: TreeList Control (Experimental)

25 Dec 2017, 00:16

This is awesome! Well done! Too bad there is so very little documentation on it :(
User avatar
SpeedMaster
Posts: 494
Joined: 12 Nov 2016, 16:09

Re: TreeList Control (Experimental)

25 Dec 2017, 08:33

Thanks for sharing :thumbup:
Can you please post a screenshot ?

(For debugging) this is what I get on my pc:
windows 7 (64 bit) - dipiscaling 125% - AHK (ANSI 32 bit) Version 1.1.27.00
Attachments
screenshot.png
screenshot.png (23.65 KiB) Viewed 6134 times
burque505
Posts: 1731
Joined: 22 Jan 2017, 19:37

Re: TreeList Control (Experimental)

25 Dec 2017, 15:00

Hi SpeedMaster, if you switch to 32-bit Unicode it'll display correctly. I also have 125% scaling, so I have the same overlap problem as you're experiencing.
Regards,
burque505
Unicode.png
Unicode.png (76.29 KiB) Viewed 6107 times
Ansi.png
Ansi.png (86.9 KiB) Viewed 6107 times
User avatar
Alguimist
Posts: 428
Joined: 05 Oct 2015, 16:41
Contact:

Re: TreeList Control (Experimental)

25 Dec 2017, 17:45

kczx3 wrote:This is awesome! Well done! Too bad there is so very little documentation on it :(
Thank you :). I had to spent some time trying to find out by trial and error the value of some constants because they do not coincide with the TreeView constants.
SpeedMaster wrote:windows 7 (64 bit) - dipiscaling 125% - AHK (ANSI 32 bit) Version 1.1.27.00
burque505 wrote:I also have 125% scaling, so I have the same overlap problem as you're experiencing.
Modifications made in the scripts:
- Added the option -DPIScale to deal with non-default DPI scaling.
- The execution of AHK ANSI is redirected to AHK Unicode 32-bit.
- The method Add was modified to use 1-based icon index.
User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: TreeList Control (Experimental)

25 Dec 2017, 20:18

Does the control fail to paint for anyone else if you rapidly resize the column headers? That’s a common issue with owner drawn listviews in AHK. I wonder if this control suffers the same fate. It seems like this is just a owner drawn listview made by Microsoft but not advertised.
guest3456
Posts: 3454
Joined: 09 Oct 2013, 10:31

Re: TreeList Control (Experimental)

26 Dec 2017, 01:15

great work Alguimist

just me
Posts: 9423
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: TreeList Control (Experimental)

26 Dec 2017, 03:49

Code: Select all

        Ptr := x64 ? "Ptr" : ""
I think it should be

Code: Select all

        Ptr := This.x64 ? "Ptr" : ""
Same for others.
User avatar
SpeedMaster
Posts: 494
Joined: 12 Nov 2016, 16:09

Re: TreeList Control (Experimental)

26 Dec 2017, 05:08

Alguimist wrote: - Added the option -DPIScale to deal with non-default DPI scaling.
- The execution of AHK ANSI is redirected to AHK Unicode 32-bit.
Works fine now :) thanks! :thumbup:
User avatar
oldbrother
Posts: 273
Joined: 23 Oct 2013, 05:08

Re: TreeList Control (Experimental)

26 Dec 2017, 06:33

I'm using Surface book, the screen resolution is 3000:2000, I have to use screen scale 250%, otherwise the fonts will be too small to see. With -DPISCALE, the GUI shows like this:
Capture.JPG
Capture.JPG (74.59 KiB) Viewed 6011 times
Capture1.JPG
Capture1.JPG (75.37 KiB) Viewed 6008 times
User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: TreeList Control (Experimental)

26 Dec 2017, 08:17

just me wrote:

Code: Select all

        Ptr := x64 ? "Ptr" : ""
I think it should be

Code: Select all

        Ptr := This.x64 ? "Ptr" : ""
Same for others.
It doesn't crash for me after updating those but the control doesn't seem to properly get items added to it still. It appears as though it has no rows at all.
User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: TreeList Control (Experimental)

26 Dec 2017, 08:35

Doesn't seem like the control properly accepts the Explorer Theme either.
User avatar
Alguimist
Posts: 428
Joined: 05 Oct 2015, 16:41
Contact:

Re: TreeList Control (Experimental)

26 Dec 2017, 14:46

Thanks, just me! It is what kczx3 said: the script no longer crashes with x64, but items cannot be added. I'm trying to figure out the reason. The structure in use is a TVINSERTSTRUCT with a LVITEM instead of a TVITEM.
oldbrother wrote:I'm using Surface book, the screen resolution is 3000:2000, I have to use screen scale 250%
As Drugwash once said: "me again with 9x issues". Replace 9x with DPI in my case. I removed the option -DPIScale in favor of a function that converts the coordinates according to A_ScreenDPI. See if it works now.
just me
Posts: 9423
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: TreeList Control (Experimental)

29 Dec 2017, 05:07

Hi Alguimist,

I'm not sure why it is needed, but at least you can add items with AHK U64 with the following changes within the Add() method:

Code: Select all

        ; TL_INSERTSTRUCT
        VarSetCapacity(TVINSERTSTRUCT, A_PtrSize == 8 ? 104 : 68, 0)
        NumPut(ParentID, TVINSERTSTRUCT, 0, "Ptr") ; hParent
        NumPut(0xFFFF0002, TVINSERTSTRUCT, A_PtrSize == 8 ? 8 : 4, "Ptr") ; hInsertAfter (TVI_LAST)                          <<<<<
        NumPut(&LVITEM, TVINSERTSTRUCT, A_PtrSize == 8 ? 16 : 8, "Ptr")   ;                                                  <<<<<
just me
Posts: 9423
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: TreeList Control (Experimental)

29 Dec 2017, 06:44

It's just me again! ;)

I added some changes to make the class work with AHK ANSI (at least partially as a result of some quick testing). Look for pszText to locate the changes, please.

Code: Select all

; TreeList Control Class (v1.0.2)

Class TreeList {
    Static TLM_INSERTITEM := 0x401
    , TLM_DELETEITEM := 0x402
    , TLM_DELETEALLITEMS := 0x403
    , TLM_GETITEM := 0x404
    , TLM_SETITEM := 0x405
    , TLM_GETITEMCOUNT := 0x406
    , TLM_GETNEXTITEM := 0x407
    , TLM_EXPAND := 0x408
    , TLM_SETIMAGELIST := 0x409
    , TLM_GETIMAGELIST := 0x40A
    , TLM_INSERTCOLUMN := 0x40B
    , TLM_DELETECOLUMN := 0x40C
    , TLM_SELECTITEM := 0x40D
    , TLM_REDRAWWINDOW := 0x40E
    , TLM_ISEXPANDED := 0x40F
    , TLM_GETCOLUMNWIDTH := 0x410
    , TLM_SETCOLUMNWIDTH := 0x411

    __New(hWndParent := 0, X := 0, Y := 0, Width := 300, Height := 200, Style := 0x5001000D, ExStyle := 0) {
        hMod := DllCall("LoadLibrary", "Str", "rasdlg.dll")
        If (!hMod) {
            MsgBox 0x10, Error, Failed to load library rasdlg.dll.
            Return
        }

        this.hWnd := DllCall("CreateWindowEx"
                     , "Uint" , ExStyle
                     , "Str"  , "TreeList"
                     , "Str"  , ""
                     , "UInt" , Style
                     , "Int"  , X
                     , "Int"  , Y
                     , "Int"  , Width
                     , "Int"  , Height
                     , "UInt" , hWndParent
                     , "UInt" , 0
                     , "UInt" , hMod
                     , "UInt" , 0, "UInt")

        If (!this.hWnd) {
            MsgBox 0x10, Error, Failed to create the TreeList control.
            Return
        }

        Ptr := A_PtrSize == 8 ? "Ptr" : ""
        this.OldWndProc := DllCall("GetWindowLong" . Ptr, "Ptr", this.hWnd, "Int", -4, "Ptr") ; GWL_WNDPROC
        _TreeListHandler(this.OldWndProc, -1, 0, 0)
        this.NewWndProc := RegisterCallback("_TreeListHandler", "", 4)
        this.OldWndProc := DllCall("SetWindowLong" . Ptr, "Ptr", this.hWnd, "Int", -4, "Ptr", this.NewWndProc, "Ptr")

        this.hLV  := this.GetListView()
        this.hHdr := this.GetHeader()
        this.hMod := hMod

        WinSet Style, +0x800000, % "ahk_id" this.hLV ; WS_BORDER
    }

    AddColumn(Text, Width := 100, Align := "", Pos := -1) {
        Static fmt := {"Left": 0, "Center": 2, "Right": 1}

        VarSetCapacity(LVCOLUMN, A_PtrSize == 8 ? 56 : 44, 0)

        Mask := 0x6 ; (LVCF_TEXT := 0x4, LVCF_WIDTH := 0x2)
        If (fmt[Align] != "") {
            Mask |= 0x1 ; LVCF_FMT
            NumPut(fmt[Align], LVCOLUMN, 4, "Int") ; fmt
        }
        pszText := A_IsUnicode ? &Text : This.WStr(Text, WText)
        NumPut(Mask, LVCOLUMN, 0, "UInt") ; mask
        NumPut(Width, LVCOLUMN, 8, "Int") ; cx
        NumPut(pszText, LVCOLUMN, A_PtrSize == 8 ? 16 : 12, "Ptr") ; pszText
        NumPut(iImage := 1, LVCOLUMN, A_PtrSize == 8 ? 32 : 24, "Int")

        If (Pos == -1) {
            Pos := this.GetColumnCount()
        }

        SendMessage, % this.TLM_INSERTCOLUMN, %Pos%, % &LVCOLUMN,, % "ahk_id" this.hWnd
    }

    SetColumnText(ColN, NewText) {
        pszText := A_IsUnicode ? &NewText : This.WStr(NewText, WText)
        VarSetCapacity(LVCOLUMN, A_PtrSize == 8 ? 56 : 44, 0)
        NumPut(0x4, LVCOLUMN, 0, "UInt") ; mask (LVCF_TEXT)
        NumPut(pszText, LVCOLUMN, A_PtrSize == 8 ? 16 : 12, "Ptr") ; pszText
        SendMessage % A_IsUnicode ? 0x1060 : 0x101A, ColN - 1, &LVCOLUMN,, % "ahk_id" . this.hLV ; LVM_SETCOLUMN
        Return ErrorLevel
    }

    DeleteColumn(ColN) {
        SendMessage % this.TLM_DELETECOLUMN, ColN - 1,,, % "ahk_id" . this.hWnd
        Return ErrorLevel
    }

    Add(ParentID := 0, Icon := "", Fields*) {
        Static TVI_LAST := -65534 & 0xFFFFFFFF ;                                                                             <<<<<
        Mask := Icon != "" ? 0x3 : 0x1 ; LVIF_TEXT = 1, LVIF_IMAGE = 0x2
        Text := Fields[1]
        pszText := A_IsUnicode ? &Text : This.WStr(Text, WText)
        VarSetCapacity(LVITEM, A_PtrSize == 8 ? 88 : 60, 0)
        NumPut(Mask, LVITEM, 0, "UInt") ; mask
        NumPut(0, LVITEM, 4, "Int") ; iItem
        NumPut(pszText, LVITEM, A_PtrSize == 8 ? 24 : 20, "Ptr") ; pszText
        If (Icon != "") {
            NumPut(Icon - 1, LVITEM, A_PtrSize == 8 ? 36 : 28, "Int") ; iImage
        }

        ; TL_INSERTSTRUCT
        VarSetCapacity(TVINSERTSTRUCT, A_PtrSize == 8 ? 104 : 68, 0)
        NumPut(ParentID, TVINSERTSTRUCT, 0, "Ptr") ; hParent
        NumPut(TVI_LAST, TVINSERTSTRUCT, A_PtrSize == 8 ? 8 : 4, "Ptr") ; hInsertAfter (TVI_LAST)                            <<<<<
        NumPut(&LVITEM, TVINSERTSTRUCT, A_PtrSize == 8 ? 16 : 8, "Ptr") ;                                                    <<<<<

        iItem := this.Send(this.TLM_INSERTITEM, 0, &TVINSERTSTRUCT)

        ; Sub items
        For Each, Field in Fields {
            If (A_Index == 1) {
                Continue
            }
            pszText := A_IsUnicode ? &Field : This.WStr(Field, WText)
            NumPut(iItem, LVITEM, 4, "Int") ; iItem
            NumPut(A_Index - 1, LVITEM, 8, "Int") ; iSubItem
            NumPut(pszText, LVITEM, A_PtrSize == 8 ? 24 : 20, "Ptr") ; pszText
            this.Send(this.TLM_SETITEM, 0, &LVITEM)
        }

        Return iItem
    }

    Delete(ItemID := "") {
        If (ItemID == "") {
            Return this.Send(this.TLM_DELETEALLITEMS, 0, 0)
        } Else {
            Return this.Send(this.TLM_DELETEITEM, 0, ItemID)
        }
    }

    Select(ItemID) {
        Return this.Send(this.TLM_SELECTITEM, 9, ItemID) ; TLGN_CARET
    }

    Expand(ItemID) {
        Return this.Send(this.TLM_EXPAND, 1, ItemID) ; 0 = toggle
    }

    IsExpanded(ItemID) {
        Return this.Send(this.TLM_ISEXPANDED, 0, ItemID)
    }

    Collapse(ItemID) {
        Return this.Send(this.TLM_EXPAND, 2, ItemID)
    }

    SetImageList(ImageListID) {
        Return this.Send(this.TLM_SETIMAGELIST, 0, ImageListID)
    }

    GetImageList() {
        Return this.Send(this.TLM_GETIMAGELIST, 0, 0)
    }

    GetCount() {
        Return this.Send(this.TLM_GETITEMCOUNT, 0, 0)
    }

    GetColumnCount() {
        SendMessage 0x1200, 0, 0,, % "ahk_id" . this.hHdr ; HDM_GETITEMCOUNT
        Return ErrorLevel
    }

    GetColumnWidth(ColN) {
        Return this.Send(this.TLM_GETCOLUMNWIDTH, ColN - 1, 0)
    }

    ; LVSCW_AUTOSIZE (-1) Automatically sizes the column.
    ; LVSCW_AUTOSIZE_USEHEADER (-2): Automatically sizes the column to fit the header text.
    ; If you use this value with the last column, its width is set to fill the remaining width of the list-view control.
    SetColumnWidth(ColN, Width := -1) {
        Static AutoSize := {"Auto": -1, "AutoHdr": -2}
        If (AutoSize[Width] != "") {
            Width := AutoSize[Width]
        }
        Return this.Send(this.TLM_SETCOLUMNWIDTH, ColN - 1, Width)
    }

    GetListView() {
        Return DllCall("GetWindow", "Ptr", this.hWnd, "UInt", 5) ; GW_CHILD
    }

    GetHeader() {
        SendMessage 0x101F, 0, 0,, % "ahk_id" . this.hLV ; LVM_GETHEADER
        Return ErrorLevel
    }

    GetSelection() {
        Return this.Send(this.TLM_GETNEXTITEM, 32, 0)
    }

    GetRoot() {
        Return this.Send(this.TLM_GETNEXTITEM, 0, 0) ; TLGN_ROOT
    }

    GetChild(ParentItemID) {
        Return this.Send(this.TLM_GETNEXTITEM, 2, ParentItemID) ; TLGN_CHILD
    }

    GetNext(ItemID := "") {
        If (ItemID == "") {
            Return this.GetRoot()
        } Else {
            Return this.Send(this.TLM_GETNEXTITEM, 4, ItemID) ; TLGN_NEXT
        }
    }

    GetPrev(ItemID) {
        Return this.Send(this.TLM_GETNEXTITEM, 8, ItemID) ; TLGN_PREVIOUS
    }

    GetParent(ItemID) {
        Return this.Send(this.TLM_GETNEXTITEM, 1, ItemID) ; 1 = TLGN_PARENT
    }

    SetText(ItemID, ColN, NewText) {
        pszText := A_IsUnicode ? &NewText : This.WStr(NewText, WText)
        VarSetCapacity(LVITEM, A_PtrSize == 8 ? 88 : 60, 0)
        NumPut(0x1, LVITEM, 0, "UInt") ; mask (TLIF_TEXT)
        NumPut(ItemID, LVITEM, 4, "Int") ; iItem
        NumPut(ColN - 1, LVITEM, 8, "Int") ; iSubItem
        NumPut(pszText, LVITEM, A_PtrSize == 8 ? 24 : 20, "Ptr") ; pszText

        Return this.Send(this.TLM_SETITEM, 0, &LVITEM)
    }

    GetText(ItemID, ColN := 1) {
        VarSetCapacity(LVITEM, A_PtrSize == 8 ? 88 : 60, 0)
        NumPut(0x1, LVITEM, 0, "UInt") ; mask (TLIF_TEXT)
        NumPut(ItemID, LVITEM, 4, "Int") ; iItem
        NumPut(ColN - 1, LVITEM, 8, "Int") ; iSubItem
        VarSetCapacity(pszText, 256, 0)
        NumPut(&pszText, LVITEM, A_PtrSize == 8 ? 24 : 20, "Ptr")
        NumPut(256, LVITEM, A_PtrSize == 8 ? 32 : 24, "Int") ; cchTextMax

        this.Send(this.TLM_GETITEM, 0, &LVITEM)
        Return StrGet(NumGet(LVITEM, A_PtrSize == 8 ? 24 : 20, "Ptr"), "UTF-16") ; pszText
    }

    SetIcon(ItemID, IconIndex) {
        VarSetCapacity(LVITEM, A_PtrSize == 8 ? 88 : 60, 0)
        NumPut(0x2, LVITEM, 0, "UInt") ; mask (TLIF_IMAGE)
        NumPut(ItemID, LVITEM, 4, "Int") ; iItem
        ;NumPut(ColN - 1, LVITEM, 8, "Int") ; iSubItem
        NumPut(IconIndex, LVITEM, A_PtrSize == 8 ? 36 : 28, "Int") ; iImage

        Return this.Send(this.TLM_SETITEM, 0, &LVITEM)
    }

    ; Get icon index in the image list
    GetIcon(ItemID) {
        VarSetCapacity(LVITEM, A_PtrSize == 8 ? 88 : 60, 0)
        NumPut(0x2, LVITEM, 0, "UInt") ; mask (TLIF_IMAGE)
        NumPut(ItemID, LVITEM, 4, "Int") ; iItem
        this.SendMessage(this.TLM_GETITEM, 0, &LVITEM)
        Return NumGet(LVITEM, A_PtrSize == 8 ? 36 : 28, "Int")
    }

    Redraw() {
        Return this.Send(this.TLM_REDRAWWINDOW, 0, 0)
    }

    SetEventHandler(EventHandler) {
        _TreeListStorage(this.hWnd, EventHandler)
    }

    Send(Msg, wParam, lParam) {
        SendMessage Msg, wParam, lParam,, % "ahk_id" . this.hWnd
        Return ErrorLevel
    }

    WStr(ByRef AStr, ByRef WStr) {
        Size := StrPut(AStr, "UTF-16")
        VarSetCapacity(WStr, Size * 2, 0)
        StrPut(ASTr, &WStr, "UTF-16")
        Return &Wstr
    }
}

_TreeListStorage(hWnd, Callback := "") {
    Static o := {}
    Return (o[hWnd] != "") ? o[hWnd] : o[hWnd] := Callback
}

_TreeListHandler(hWnd, msg, wParam, lParam) {
    Static n := {-2: "Click", -3: "DoubleClick", -5: "RightClick", -7: "SetFocus", -8: "KillFocus", -155: "KeyDown"}
    Static OldWndProc
    If (msg == -1) {
        OldWndProc := hWnd
    }

    If (msg == 78) { ; WM_NOTIFY (0x4E)
        hWndFrom := NumGet(lParam + 0)
        idFrom := NumGet(lParam + 4)
        Code := NumGet(lParam + 0, A_PtrSize * 2, "Int")

        If (Code > -7) { ; NM_CLICK, NM_DBLCLK, NM_RCLICK
            Row := NumGet(lParam + 0, A_PtrSize == 8 ? 24 : 12, "Int") + 1 ; NMITEMACTIVATE iItem
            Col := NumGet(lParam + 0, A_PtrSize == 8 ? 28 : 16, "Int") + 1 ; NMITEMACTIVATE iSubItem
        } Else If (Code == -155) { ; LVN_KEYDOWN
            Key := NumGet(lParam + 0, A_PtrSize == 8 ? 24 : 12, "Short") ; NMLVKEYDOWN wVKey (key code)
        }

        Handler := _TreeListStorage(hWnd)
        If (Handler != "") {
            Event := (n[Code] != "") ? n[Code] : Code
            %Handler%(hWnd, Event, Row, Col, Key)
        }
    }

    Return DllCall("CallWindowProcA", "Ptr", OldWndProc, "Ptr", hWnd, "UInt", msg, "Ptr", wParam, "Ptr", lParam)
}
User avatar
Alguimist
Posts: 428
Joined: 05 Oct 2015, 16:41
Contact:

Re: TreeList Control (Experimental)

29 Dec 2017, 18:11

Thank you :D. Files updated.
HotKeyIt
Posts: 2364
Joined: 29 Sep 2013, 18:35
Contact:

Re: TreeList Control (Experimental)

29 Dec 2017, 21:00

This looks really nice, thanks for sharing.

After changing following lines, it runs fine on 64-bit for me:
Before DllCall("FreeLibrary", "Ptr", TL.hMod) we need to call Gui, Destroy.
On 64-bit LoadLibrary needs to return PTR: hMod := DllCall("LoadLibrary", "Str", "rasdlg.dll","PTR").
Same for CreateWindowEx: this.hWnd := DllCall("CreateWindowEx", "Uint" , ExStyle, "Str" , "TreeList", "Str" , "", "UInt" , Style, "Int" , X, "Int" , Y, "Int" , Width, "Int" , Height, "PTR" , hWndParent, "UInt" , 0, "PTR" , hMod, "UInt" , 0, "PTR").

I have also converted your code to v2 ;)

Code: Select all

; TreeList Test

#SingleInstance Force

;~ #Include TreeList.ahk

TraySetIcon("shell32.dll", 42)
global Gui,SB
(Gui:=guiCreate("+Resize","TreeList Test")).OnEvent("Size","GuiSize")
Gui.OnEvent("Escape","GuiClose")
Gui.OnEvent("Close","GuiClose")
Gui.SetFont("s9", "Segoe UI")
Gui.BackColor :="White"

(Gui.AddButton("x8 y8   w100 h23", "Add Root")).OnEvent("Click","AddRoot")
(Gui.AddButton("x8 y38  w100 h23", "Add Child")).OnEvent("Click","AddChild")
(Gui.AddButton("x8 y68  w100 h23", "Get Text")).OnEvent("Click","GetText")
(Gui.AddButton("x8 y98  w100 h23", "Change Item")).OnEvent("Click","ChangeItem")
(Gui.AddButton("x8 y128 w100 h23", "Select Parent")).OnEvent("Click","SelectParent")
(Gui.AddButton("x8 y158 w100 h23", "Select Child")).OnEvent("Click","SelectChild")
(Gui.AddButton("x8 y188 w100 h23", "Select Next")).OnEvent("Click","SelectNext")
(Gui.AddButton("x8 y218 w100 h23", "Expand")).OnEvent("Click","Expand")
(Gui.AddButton("x8 y248 w100 h23", "Collapse")).OnEvent("Click","Collapse")
(Gui.AddButton("x8 y278 w100 h23", "Toggle Icons")).OnEvent("Click","ToggleIcons")
(Gui.AddButton("x8 y308 w100 h23", "Toggle Grid")).OnEvent("Click","ToggleGrid")
(Gui.AddButton("x8 y338 w100 h23", "Delete")).OnEvent("Click","Delete")
(Gui.AddButton("x8 y368 w100 h23", "Delete All")).OnEvent("Click","DeleteAll")
(Gui.AddButton("x8 y398 w100 h23", "Reload")).OnEvent("Click","ReloadItems")

; TreeList ImageList
Global ImageListID := IL_Create(12)
Loop 10 {
    IL_Add(ImageListID, "shell32.dll", A_Index)
}
IL_Add(ImageListID, "shell32.dll", 70)
IL_Add(ImageListID, "shell32.dll", 71)

; Create the TreeList
Global TL := New TreeList(gui.hwnd, DPIScale(117), DPIScale(8), DPIScale(510), DPIScale(413))

TL.SetImageList(ImageListID)

; Add columns (mandatory)
TL.AddColumn("Column 1", DPIScale(289))
TL.AddColumn("Column 2", DPIScale(95))
TL.AddColumn("Column 3", DPIScale(123))

LoadItems()

TL.SetEventHandler("OnTreeList")

SB:=Gui.AddStatusBar()
SB.SetParts(120, 80, 80)

Gui.Show("w635 h453")
Return

GuiSize(gui,minmax,width, height){
    ControlMove ,, DPIScale(width - 125), DPIScale(height - 40),, "ahk_id " . TL.hWnd
    ControlMove ,, DPIScale(width - 125), DPIScale(height - 40),, "ahk_id " . TL.hLV
}

GuiClose(){
    Gui.Destroy()
    DllCall("FreeLibrary", "Ptr", TL.hMod)
    ExitApp
}

; Default items
LoadItems() {
    DateTime := GetDateTime()

    RootID1 := TL.Add(0, "4", "Files", "<DIR>", DateTime)
    TL.Add(RootID1, "12", "Commands.txt", "5 KB", DateTime)
    TL.Add(RootID1, "2", "Database.xml", "7 MB", DateTime)

    RootID2 := TL.Add(0, "4", "Settings", "<DIR>", DateTime)
    TL.Add(RootID2, "11", "Settings.ini", "1 KB", DateTime)
    TL.Add(RootID2, "2", "Config.xml", "2 KB", DateTime)

    TL.Expand(RootID1)
    TL.Expand(RootID2)
    TL.Select(RootID1)
}

GetDateTime() {
    DateTime:=FormatTime("D1")
    DateTime := RegExReplace(DateTime, "(.*)\s(.*)", "$2 $1")
    Return DateTime
}

AddRoot() {
    Folder := GetRandomName()
    DateTime := GetDateTime()
    ItemID := TL.Add(0, "4", Folder, "<DIR>", DateTime)
    TL.Select(ItemID)
}

AddChild() {
    Filename := GetRandomName() . ".EXT"
    DateTime := GetDateTime()

    ParentID := TL.GetSelection()
    Size := TL.GetText(ParentID, 2)
    If (Size != "<DIR>") {
        TL.SetIcon(ParentID, 4)
        Name := TL.GetText(ParentID, 1)
        Name := SubStr(Name, 1, InStr(Name, ".") - 1)
        TL.SetText(ParentID, 1, Name)
        TL.SetText(ParentID, 2, "<DIR>")
    }

    ItemId := TL.Add(ParentID, "1", Filename, "1 KB", DateTime)
    TL.Expand(ParentID)
    TL.Select(ItemID)
}

GetText() {
    Col1 := TL.GetText(TL.GetSelection(), 1)
    Col2 := TL.GetText(TL.GetSelection(), 2)
    Col3 := TL.GetText(TL.GetSelection(), 3)

    Gui.Opt("+OwnDialogs")
    MsgBox("Column 1:  " . Col1 . "`nColumn 2:  " . Col2 . "`nColumn 3:  " Col3, "TreeList", 0x40)
}

ChangeItem() {
    ItemID := TL.GetSelection()
    Size := TL.GetText(ItemID, 2)
    Name := GetRandomName()
    If (Size != "<DIR>") {
        Name .= ".EXT"
        TL.SetIcon(ItemID, 0)
    }

    TL.SetText(ItemID, 1, Name)
    TL.SetText(ItemID, 3, GetDateTime())
}

SelectParent() {
    TL.Select(TL.GetParent(TL.GetSelection()))
}

SelectChild() {
    TL.Select(TL.GetChild(TL.GetSelection()))
}

SelectNext() {
    TL.Select(TL.GetNext(TL.GetSelection()))
}

Expand() {
    TL.Expand(TL.GetSelection())
}

Collapse() {
    TL.Collapse(TL.GetSelection())
}

ToggleIcons() {
    If (TL.GetImageList()) {
        TL.SetImageList(0)
    } Else {
        TL.SetImageList(ImageListID)
    }
}

ToggleGrid() {
    ErrorLevel:=SendMessage(0x1037,,,, "ahk_id " . TL.hLV) ; LVM_GETEXTENDEDLISTVIEWSTYLE
    ExtStyle := ErrorLevel & 1 ? 0 : 1
    SendMessage 0x1036, 1, ExtStyle,, "ahk_id " . TL.hLV ; LVM_SETEXTENDEDLISTVIEWSTYLE
}

Delete() {
    TL.Delete(TL.GetSelection())
}

DeleteAll() {
    TL.Delete()
}

ReloadItems() {
    DeleteAll()
    DeleteAll() ; ?
    LoadItems()
}

GetRandomName() {
    String := ""
    Loop 8
        String .= Chr(Random(65, 90))
    Return String
}

OnTreeList(hWnd, Event, Row, Col, Key) {
    If (Event == "Click") {
        SB.SetText(" ID: " . TL.GetSelection())
        SB.SetText("Row: " . Row, 2)
        SB.SetText("Column: " . Col, 3)
    }
    Else If (Event == "RightClick") {
        ContextMenu:=MenuCreate()
        ContextMenu.Add("Get Text", "GetText")
        ContextMenu.Show()
    }
    Else If (Event == "DoubleClick") {
        GetText()
    }
}

DPIScale(x) {
    Return (x * A_ScreenDPI) // 96
}




; TreeList Control Class (v1.1.0)
; Credits to just me for x64 and ANSI compatibility.

Class TreeList {
    Static TLM_INSERTITEM := 0x401
    , TLM_DELETEITEM := 0x402
    , TLM_DELETEALLITEMS := 0x403
    , TLM_GETITEM := 0x404
    , TLM_SETITEM := 0x405
    , TLM_GETITEMCOUNT := 0x406
    , TLM_GETNEXTITEM := 0x407
    , TLM_EXPAND := 0x408
    , TLM_SETIMAGELIST := 0x409
    , TLM_GETIMAGELIST := 0x40A
    , TLM_INSERTCOLUMN := 0x40B
    , TLM_DELETECOLUMN := 0x40C
    , TLM_SELECTITEM := 0x40D
    , TLM_REDRAWWINDOW := 0x40E
    , TLM_ISEXPANDED := 0x40F
    , TLM_GETCOLUMNWIDTH := 0x410
    , TLM_SETCOLUMNWIDTH := 0x411

    __New(hWndParent := 0, X := 0, Y := 0, Width := 300, Height := 200, Style := 0x5001000D, ExStyle := 0) {
        hMod := DllCall("LoadLibrary", "Str", "rasdlg.dll","PTR")
        If (!hMod) {
            MsgBox "Failed to load library rasdlg.dll.", "Error", 0x10
            Return
        }

        this.hWnd := DllCall("CreateWindowEx"
                     , "Uint" , ExStyle
                     , "Str"  , "TreeList"
                     , "Str"  , ""
                     , "UInt" , Style
                     , "Int"  , X
                     , "Int"  , Y
                     , "Int"  , Width
                     , "Int"  , Height
                     , "PTR" , hWndParent
                     , "UInt" , 0
                     , "PTR" , hMod
                     , "UInt" , 0, "PTR")

        If (!this.hWnd) {
            MsgBox "Failed to create the TreeList control.", "Error", 0x10
            Return
        }

        Ptr := A_PtrSize == 8 ? "Ptr" : ""
        this.OldWndProc := DllCall("GetWindowLong" . Ptr, "Ptr", this.hWnd, "Int", -4, "Ptr") ; GWL_WNDPROC
        _TreeListHandler(this.OldWndProc, -1, 0, 0)
        this.NewWndProc := RegisterCallback("_TreeListHandler", "", 4)
        this.OldWndProc := DllCall("SetWindowLong" . Ptr, "Ptr", this.hWnd, "Int", -4, "Ptr", this.NewWndProc, "Ptr")

        this.hLV  := this.GetListView()
        this.hHdr := this.GetHeader()
        this.hMod := hMod

        WinSetStyle "+0x800000", "ahk_id " this.hLV ; WS_BORDER
    }

    AddColumn(Text, Width := 100, Align := "", Pos := -1) {
        Static fmt := {"Left": 0, "Center": 2, "Right": 1}

        VarSetCapacity(LVCOLUMN, A_PtrSize == 8 ? 56 : 44, 0)

        Mask := 0x6 ; (LVCF_TEXT := 0x4, LVCF_WIDTH := 0x2)
        If (fmt[Align] != "") {
            Mask |= 0x1 ; LVCF_FMT
            NumPut(fmt[Align], LVCOLUMN, 4, "Int") ; fmt
        }
        NumPut(Mask, LVCOLUMN, 0, "UInt") ; mask
        NumPut(Width, LVCOLUMN, 8, "Int") ; cx
        NumPut(&Text, LVCOLUMN, A_PtrSize == 8 ? 16 : 12, "Ptr") ; pszText
        NumPut(iImage := 1, LVCOLUMN, A_PtrSize == 8 ? 32 : 24, "Int")

        If (Pos == -1) {
            Pos := this.GetColumnCount()
        }

        SendMessage this.TLM_INSERTCOLUMN, Pos, &LVCOLUMN,, "ahk_id " this.hWnd
    }

    SetColumnText(ColN, NewText) {
        VarSetCapacity(LVCOLUMN, A_PtrSize == 8 ? 56 : 44, 0)
        NumPut(0x4, LVCOLUMN, 0, "UInt") ; mask (LVCF_TEXT)
        NumPut(&NewText, LVCOLUMN, A_PtrSize == 8 ? 16 : 12, "Ptr") ; pszText
        Return SendMessage(0x1060, ColN - 1, &LVCOLUMN,, "ahk_id " . this.hLV) ; LVM_SETCOLUMN
    }

    DeleteColumn(ColN) {
        Return SendMessage(this.TLM_DELETECOLUMN, ColN - 1,,, "ahk_id " . this.hWnd)
    }

    Add(ParentID := 0, Icon := "", Fields*) {
        Static TVI_LAST := -65534 & 0xFFFFFFFF 

        Mask := Icon != "" ? 0x3 : 0x1 ; LVIF_TEXT = 1, LVIF_IMAGE = 0x2
        Text := Fields[1]
        VarSetCapacity(LVITEM, A_PtrSize == 8 ? 88 : 60, 0)
        NumPut(Mask, LVITEM, 0, "UInt") ; mask
        NumPut(0, LVITEM, 4, "Int") ; iItem
        NumPut(&text, LVITEM, A_PtrSize == 8 ? 24 : 20, "Ptr") ; pszText
        If (Icon != "") {
            NumPut(Icon - 1, LVITEM, A_PtrSize == 8 ? 36 : 28, "Int") ; iImage
        }

        ; TL_INSERTSTRUCT
        VarSetCapacity(TVINSERTSTRUCT, A_PtrSize == 8 ? 104 : 68, 0)
        NumPut(ParentID, TVINSERTSTRUCT, 0, "Ptr") ; hParent
        NumPut(TVI_LAST, TVINSERTSTRUCT, A_PtrSize == 8 ? 8 : 4, "Ptr") ; hInsertAfter
        NumPut(&LVITEM, TVINSERTSTRUCT, A_PtrSize == 8 ? 16 : 8, "Ptr")

        iItem := this.Send(this.TLM_INSERTITEM, 0, &TVINSERTSTRUCT)

        ; Sub items
        For Each, Field in Fields {
            If (A_Index == 1) {
                Continue
            }
            NumPut(iItem, LVITEM, 4, "Int") ; iItem
            NumPut(A_Index - 1, LVITEM, 8, "Int") ; iSubItem
            NumPut(&Field, LVITEM, A_PtrSize == 8 ? 24 : 20, "Ptr") ; pszText
            this.Send(this.TLM_SETITEM, 0, &LVITEM)
        }

        Return iItem
    }

    Delete(ItemID := "") {
        If (ItemID == "") {
            Return this.Send(this.TLM_DELETEALLITEMS, 0, 0)
        } Else {
            Return this.Send(this.TLM_DELETEITEM, 0, ItemID)
        }
    }

    Select(ItemID) {
        Return this.Send(this.TLM_SELECTITEM, 9, ItemID) ; TLGN_CARET
    }

    Expand(ItemID) {
        Return this.Send(this.TLM_EXPAND, 1, ItemID) ; 0 = toggle
    }

    IsExpanded(ItemID) {
        Return this.Send(this.TLM_ISEXPANDED, 0, ItemID)
    }

    Collapse(ItemID) {
        Return this.Send(this.TLM_EXPAND, 2, ItemID)
    }

    SetImageList(ImageListID) {
        Return this.Send(this.TLM_SETIMAGELIST, 0, ImageListID)
    }

    GetImageList() {
        Return this.Send(this.TLM_GETIMAGELIST, 0, 0)
    }

    GetCount() {
        Return this.Send(this.TLM_GETITEMCOUNT, 0, 0)
    }

    GetColumnCount() {
        Return SendMessage(0x1200, 0, 0,, "ahk_id " . this.hHdr) ; HDM_GETITEMCOUNT
    }

    GetColumnWidth(ColN) {
        Return this.Send(this.TLM_GETCOLUMNWIDTH, ColN - 1, 0)
    }

    ; LVSCW_AUTOSIZE (-1) Automatically sizes the column.
    ; LVSCW_AUTOSIZE_USEHEADER (-2): Automatically sizes the column to fit the header text.
    ; If you use this value with the last column, its width is set to fill the remaining width of the list-view control.
    SetColumnWidth(ColN, Width := -1) {
        Static AutoSize := {"Auto": -1, "AutoHdr": -2}
        If (AutoSize[Width] != "") {
            Width := AutoSize[Width]
        }
        Return this.Send(this.TLM_SETCOLUMNWIDTH, ColN - 1, Width)
    }

    GetListView() {
        Return DllCall("GetWindow", "Ptr", this.hWnd, "UInt", 5, "PTR") ; GW_CHILD
    }

    GetHeader() {
        Return SendMessage(0x101F, 0, 0,, "ahk_id " . this.hLV) ; LVM_GETHEADER
    }

    GetSelection() {
        Return this.Send(this.TLM_GETNEXTITEM, 32, 0)
    }

    GetRoot() {
        Return this.Send(this.TLM_GETNEXTITEM, 0, 0) ; TLGN_ROOT
    }

    GetChild(ParentItemID) {
        Return this.Send(this.TLM_GETNEXTITEM, 2, ParentItemID) ; TLGN_CHILD
    }

    GetNext(ItemID := "") {
        If (ItemID == "") {
            Return this.GetRoot()
        } Else {
            Return this.Send(this.TLM_GETNEXTITEM, 4, ItemID) ; TLGN_NEXT
        }
    }

    GetPrev(ItemID) {
        Return this.Send(this.TLM_GETNEXTITEM, 8, ItemID) ; TLGN_PREVIOUS
    }

    GetParent(ItemID) {
        Return this.Send(this.TLM_GETNEXTITEM, 1, ItemID) ; TLGN_PARENT
    }

    SetText(ItemID, ColN, NewText) {
        VarSetCapacity(LVITEM, A_PtrSize == 8 ? 88 : 60, 0)
        NumPut(0x1, LVITEM, 0, "UInt") ; mask (TLIF_TEXT)
        NumPut(ItemID, LVITEM, 4, "Int") ; iItem
        NumPut(ColN - 1, LVITEM, 8, "Int") ; iSubItem
        NumPut(&NewText, LVITEM, A_PtrSize == 8 ? 24 : 20, "Ptr") ; pszText

        Return this.Send(this.TLM_SETITEM, 0, &LVITEM)
    }

    GetText(ItemID, ColN := 1) {
        VarSetCapacity(LVITEM, A_PtrSize == 8 ? 88 : 60, 0)
        NumPut(0x1, LVITEM, 0, "UInt") ; mask (TLIF_TEXT)
        NumPut(ItemID, LVITEM, 4, "Int") ; iItem
        NumPut(ColN - 1, LVITEM, 8, "Int") ; iSubItem
        VarSetCapacity(pszText, 256, 0)
        NumPut(&pszText, LVITEM, A_PtrSize == 8 ? 24 : 20, "Ptr")
        NumPut(256, LVITEM, A_PtrSize == 8 ? 32 : 24, "Int") ; cchTextMax

        this.Send(this.TLM_GETITEM, 0, &LVITEM)
        Return StrGet(NumGet(LVITEM, A_PtrSize == 8 ? 24 : 20, "Ptr"), "UTF-16") ; pszText
    }

    SetIcon(ItemID, IconIndex) {
        VarSetCapacity(LVITEM, A_PtrSize == 8 ? 88 : 60, 0)
        NumPut(0x2, LVITEM, 0, "UInt") ; mask (TLIF_IMAGE)
        NumPut(ItemID, LVITEM, 4, "Int") ; iItem
        NumPut(IconIndex, LVITEM, A_PtrSize == 8 ? 36 : 28, "Int") ; iImage

        Return this.Send(this.TLM_SETITEM, 0, &LVITEM)
    }

    ; Get icon index in the image list
    GetIcon(ItemID) {
        VarSetCapacity(LVITEM, A_PtrSize == 8 ? 88 : 60, 0)
        NumPut(0x2, LVITEM, 0, "UInt") ; mask (TLIF_IMAGE)
        NumPut(ItemID, LVITEM, 4, "Int") ; iItem
        this.Send(this.TLM_GETITEM, 0, &LVITEM)
        Return NumGet(LVITEM, A_PtrSize == 8 ? 36 : 28, "Int") + 1
    }

    Redraw() {
        Return this.Send(this.TLM_REDRAWWINDOW, 0, 0)
    }

    SetEventHandler(EventHandler) {
        _TreeListStorage(this.hWnd, EventHandler)
    }

    Send(Msg, wParam, lParam) {
        Return SendMessage(Msg, wParam, lParam,, "ahk_id " . this.hWnd)
    }
}

_TreeListStorage(hWnd, Callback := "") {
    Static o := {}
    Return (o[hWnd] != "") ? o[hWnd] : o[hWnd] := Callback
}

_TreeListHandler(hWnd, msg, wParam, lParam) {
    Static n := {-2: "Click", -3: "DoubleClick", -5: "RightClick", -7: "SetFocus", -8: "KillFocus", -155: "KeyDown"}
    Static OldWndProc
    If (msg == -1) {
        OldWndProc := hWnd
    }

    If (msg == 78) { ; WM_NOTIFY (0x4E)
        hWndFrom := NumGet(lParam + 0)
        idFrom := NumGet(lParam + 4)
        Code := NumGet(lParam + 0, A_PtrSize * 2, "Int")

        If (Code > -7) { ; NM_CLICK, NM_DBLCLK, NM_RCLICK
            Row := NumGet(lParam + 0, A_PtrSize == 8 ? 24 : 12, "Int") + 1 ; NMITEMACTIVATE iItem
            Col := NumGet(lParam + 0, A_PtrSize == 8 ? 28 : 16, "Int") + 1 ; NMITEMACTIVATE iSubItem
        } Else If (Code == -155) { ; LVN_KEYDOWN
            Key := NumGet(lParam + 0, A_PtrSize == 8 ? 24 : 12, "Short") ; NMLVKEYDOWN wVKey (key code)
        }

        Handler := _TreeListStorage(hWnd)
        If (Handler != "") {
            Event := (n[Code] != "") ? n[Code] : Code
            %Handler%(hWnd, Event, Row, Col, Key)
        }
    }
    Return DllCall("CallWindowProcA", "Ptr", OldWndProc, "Ptr", hWnd, "UInt", msg, "Ptr", wParam, "Ptr", lParam)
}
User avatar
Alguimist
Posts: 428
Joined: 05 Oct 2015, 16:41
Contact:

Re: TreeList Control (Experimental)

30 Dec 2017, 00:00

Thanks, HotKeyIt :D. Another update in the code.
User avatar
tidbit
Posts: 1272
Joined: 29 Sep 2013, 17:15
Location: USA

Re: TreeList Control (Experimental)

30 Dec 2017, 12:11

this looks awesome :O

though, any reason you didn't use gui,add,custom? https://autohotkey.com/docs/commands/Gu ... htm#Custom
I'm still waiting for some genius (like you :P) to make a something with gui,custom besides the default IP control example. I have absolutely no idea where to even begin. I have tried a few times
rawr. fear me.
*poke*
Is it December 21, 2012 yet?
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: TreeList Control (Experimental)

30 Dec 2017, 12:47

- @tidbit: I did some simple examples with 4 controls here:
control zoo (AHK v1.1) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=30652
Internet Explorer_Server (categorised as ActiveX, not Custom)
RICHEDIT50W
Scintilla
ToolbarWindow32
- I agree that getting started is the most difficult bit, tidbit.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: gwarble and 122 guests