Jump to content

Storing data with tree view items?


GaryC
 Share

Recommended Posts

Hi All,

I want to store an index with each tree view item so that I can locate the information associated with each item. I am trying to do this by storing the index into an array in the LPARAM member of the TVITEM structure. I wrote GUICtrlTreeViewInsertItem2, a modification of _GUICtrlTreeViewInsertItem from GUITreeView.au3 to store this information, and wrote _GUICtrlTreeViewGetParam modeled after _GUICtrlTreeViewGetText. Well, I'm not getting my data back, I can't tell if the param data isn't being stored or isn't being retrieved. I also notice that _GUICtrlTreeViewGetText doesn't retrieve the text of the item. The platform SDK documentation for the TVM_GETITEM message indicates that the text buffer returned might not be the same as the one supplied, so the below function _GUICtrlTreeViewGetText2 attempts to deal with this with no change in my results.

Also, I notice that the _GUICtrlTreeViewGetParentID UDF returns the content of the PARAM member, but I can't find anything that sets it. Does the standard AutoIt GUICtrlCreateTreeViewItem function store the control ID of the item in it?

Is there a better way of linking the items in the tree view with data when the data for each item includes more than the text that appears in the displayed item? I thought I remembered some way to get the index of the focused item in the tree view as if it were a list but I can't find anything like that.

What I am trying to do is convert HTML help (.chm) files to text. From the decompiled TOC (.hhc) file I generate the TOC in an array. I then want to display the TOC so that the user can select the node in the help to convert. Below is a test version based on my application that demonstrates what I am doing. The $gaNodes[$entry][$field] array contains 3 fields per entry: (0) the level number, (1) the item text, (2) a file name associated with the item, which this code does nothing with. I wrote push() and pop() because it didn't seem like the UDFs in array.au3 did exactly what I wanted.

Steps to reproduce:

Run this file.

When GUI appears, arrow down to select an item, such as "item 3".

Press TAB twice to reach Info button, press Spacebar to activate. This is supposed to get the stored index of the currently selected item and place the item text from the array ($gaNodes[$itm][1]) in the input box.

Notice the output in the input box.

Press ESC to terminate the GUI.

Debug info has been written to the console.

Can anyone help?

Thanks.

GaryC

#Include <array.au3>
#include <GUITreeView.au3>

Global $ghForm2 = 0

Global $gaNodes[6][3] = [[0, "item 1", ""], [0, "item 2", ""], [1, "item 2.1", ""], [1, "item 2.2", ""], _
[0, "item 3", ""], [0, "item 4", ""]]

Form2Create()
Form2MsgLoop()
GUIDelete()
Exit(0)

Func Form2Create()
$ghForm2 = GUICreate("CHM to Text - Choose starting node", 342, 183, 341, 278, -1798701056, 256)
$IDC_TREEVIEWLABEL = GUICtrlCreateLabel("Choose Node", 14, 14, 65, 16, 1342308608, 0)
Global $IDC_TREEVIEW = GUICtrlCreateTreeView(85, 14, 200, 100, 1350631424, 0)
Global $IDC_InfoInput = GUICtrlCreateInput("", 14, 128, 200, 16, 1342374016, 0)
Global $IDC_InfoBtn = GUICtrlCreateButton("Info", 222, 128, 100, 16, 1342246656, 0)
GUISetState(@SW_SHOW)
BuildTreeView($IDC_TreeView, $gaNodes)
GUICtrlSetState($IDC_TreeView, $GUI_Focus)
GUICtrlSetState(GUICtrlSendMsg($IDC_TreeView, $TVM_GETNEXTITEM, $TVGN_ROOT, 0), $GUI_Focus)
EndFunc  ; Form2Create

Func Form2MsgLoop()
While 1
Local $msg = GUIGetMsg()
Switch($msg)
Case 0
    ContinueLoop
Case $GUI_EVENT_CLOSE
    ExitLoop
Case $IDC_InfoBtn
Local $itm = GUICtrlRead($IDC_TREEVIEW)
ConsoleWrite("CtrlRead returned Selected item is " & $itm & @CRLF)  ; debug
ConsoleWrite(" Text = " & String(_GUICtrlTreeViewGetText2($IDC_TREEVIEW, $itm)) & @CRLF)  ; debug
ConsoleWrite(" param = " & String(_GUICtrlTreeViewGetParam($IDC_TREEVIEW, $itm)) & @CRLF)  ; debug
GUICtrlSetData($IDC_InfoInput,  $gaNodes[_GUICtrlTreeViewGetParam($IDC_TREEVIEW, _
GUICtrlRead($IDC_TREEVIEW))][1])
EndSwitch
ConsoleWrite(String($msg) & @CRLF)  ; debug
WEnd
EndFunc  ; Form2MsgLoop

; Build the contents of the tree view.
; $vID - ID of tree view
; $aNodes - Array of nodes, passed by reference for efficiency, should not be modified here.
Func BuildTreeView($vID, ByRef $aNodes)
Local $aParents = ""  ; empty stack
Local $iParent = 0; tell GUITreeViewInsertItem2 to start at the root
Local $iLastItem  ; last item generated
Local $iLastLevel = $aNodes[0][0]  ; start at root, we won't have a level change first time through
for $iItem = 0 to UBound($aNodes) - 1
    If ($aNodes[$iItem][0] > $iLastLevel) Then
        Push($aParents, $iParent)
        $iParent = $iLastItem
        $iLastLevel = $aNodes[$iItem][0]
    ElseIf ($aNodes[$iItem][0] < $iLastLevel) Then
        $iParent = Pop($aParents)
        $iLastLevel = $aNodes[$iItem][0]
    EndIf
    $iLastItem = _GUICtrlTreeViewInsertItem2($IDC_TREEVIEW, $aNodes[$iItem][1], $iParent, 0, $iItem)  ; add TOC name to tree with item idx in PARAM data
    If (not $iLastItem) Then
        ConsoleWrite("BuildTreeView: GUITreeViewInsertItem2 returned 0 at $aNodes[" & $iItem & "], @error = " & @error & @CRLF)  ; debug
    EndIf
Next  ; $iItem
EndFunc; BuildTreeView

; Start the stack by passing a non-array variable for $a.  It will be redefined to be an array.
Func Push(ByRef $a, $v)
If (not IsArray($a)) Then
    Local $ary[1]
    $a = $ary
Else
    Redim $a[UBound($a) + 1]
EndIf
$a[UBound($a) - 1] = $v
EndFunc  ; Push

Func Pop(ByRef $a)
If (not IsArray($a)) Then
Return SetError(1, @extend, "") 
EndIf
Local $v = $a[UBound($a) - 1]
If (UBound($a) > 1) Then
    ReDim $a[UBound($a) - 1]
Else
    $a = ""  ; last element returned
EndIf
Return $v
EndFunc  ; pop

; Same as GUITreeViewInsertItem but also stores value in the lparam field.
Func _GUICtrlTreeViewInsertItem2($i_treeview, $s_itemtext, $h_item_parent = 0, $h_item_after = 0, $iData = 0)
    If Not _IsClassName ($i_treeview, "SysTreeView32") Then Return SetError(-1, -1, 0)
    Local $h_item_tmp
    Local $st_TVI = DllStructCreate("uint;uint;" & $s_TVITEMEX)
    If @error Then Return SetError(@error, @error, 0)
    
    Local $st_text = DllStructCreate("char[" & StringLen($s_itemtext) + 1 & "]")
    If @error Then Return SetError(@error, @error, 0)
    
    If $h_item_parent = 0 Then
        $h_item_parent = $TVI_ROOT
    Else
        $h_item_tmp = GUICtrlGetHandle($h_item_parent)
        If $h_item_tmp <> 0 Then $h_item_parent = $h_item_tmp
    EndIf
    
    If $h_item_after = 0 Or _
            ($h_item_after <> $TVI_ROOT And _
            $h_item_after <> $TVI_FIRST And _
            $h_item_after <> $TVI_LAST And _
            $h_item_after <> $TVI_SORT) Then
        $h_item_after = $TVI_LAST
    EndIf
    
    DllStructSetData($st_text, 1, $s_itemtext)
    
    Local $u_mask = BitOR($TVIF_TEXT, $TVIF_PARAM)
    
    Local $h_icon = _TreeViewGetImageListIconHandle($i_treeview, 0)
    If $h_icon <> 0 Then
        $u_mask = BitOR($u_mask, $TVIF_IMAGE)
        DllStructSetData($st_TVI, 9, 0)
        DllCall("user32.dll", "int", "DestroyIcon", "hwnd", $h_icon)
    EndIf
    
    $h_icon = _TreeViewGetImageListIconHandle($i_treeview, 1)
    If $h_icon <> 0 Then
        $u_mask = BitOR($u_mask, $TVIF_SELECTEDIMAGE)
        DllStructSetData($st_TVI, 10, 0)
        DllCall("user32.dll", "int", "DestroyIcon", "hwnd", $h_icon)
    EndIf
    
    DllStructSetData($st_TVI, 1, $h_item_parent)
    DllStructSetData($st_TVI, 2, $h_item_after)
    DllStructSetData($st_TVI, 3, $u_mask)
    DllStructSetData($st_TVI, 7, DllStructGetPtr($st_text))
    DLLStructSetData(   $st_TVI, 12, $iData)

    Return GUICtrlSendMsg($i_treeview, $TVM_INSERTITEM, 0, DllStructGetPtr($st_TVI))
EndFunc  ;==>_GUICtrlTreeViewInsertItem2

; Returns lparam field.
Func _GUICtrlTreeViewGetParam($i_treeview, $h_item = 0)
    If Not _IsClassName ($i_treeview, "SysTreeView32") Then Return SetError(-1, -1, "")
    Local $iData = 0
    
    ConsoleWrite("_GUICtrlTreeViewGetParam: Initial $h_item = " & $h_item & @CRLF)  ; debug
    $h_item = _TreeViewGetItemHandle($i_treeview, $h_item)
    If $h_item = 0 Then Return SetError(1, 1, "")
    
    Local $st_TVITEM = DllStructCreate($s_TVITEMEX)
    If @error Then Return SetError(@error, @error, "")
    
    DllStructSetData($st_TVITEM, 1, $TVIF_PARAM)
    DllStructSetData($st_TVITEM, 2, $h_item)
    
    Local $iRtn = GUICtrlSendMsg($i_treeview, $TVM_GETITEM, 0, DllStructGetPtr($st_TVITEM))
    Local $sDbg = "st_TVItem: "  ; debug
    For $iDbg = 1 to 10  ; debug
        $sDbg &= @CRLF & $iDbg & ": " & DllStructGetData($st_TVItem, $iDbg) & " 0x" & Hex(DllStructGetData($st_TVItem, $iDbg))  ; debug
    Next  ; $iDbg  ; debug
    ConsoleWrite("_GUICtrlTreeViewGetParam: " & $sDbg & @CRLF)  ; debug
    If ($iRtn) Then
        $iData = DllStructGetData($st_TVITEM, 10)
    Else
        Return SetError(1, @error, 0)
    EndIf
    
    Return $iData
EndFunc  ;==>_GUICtrlTreeViewGetParam


; GUITreeViewGetText with code to handle the case where the system returns text in a different buffer (see PSDK for TVM_GETITEM).
Func _GUICtrlTreeViewGetText2($i_treeview, $h_item = 0)
    If Not _IsClassName ($i_treeview, "SysTreeView32") Then Return SetError(-1, -1, "")
    Local $s_text = ""
    
    $h_item = _TreeViewGetItemHandle($i_treeview, $h_item)
    If $h_item = 0 Then Return SetError(1, 1, "")
    
    Local $st_text = DllStructCreate("char[4096]"); create a text 'area' for receiving the text
    If @error Then Return SetError(@error, @error, "")
    
    Local $st_TVITEM = DllStructCreate($s_TVITEMEX)
    If @error Then Return SetError(@error, @error, "")
    
    DllStructSetData($st_TVITEM, 1, $TVIF_TEXT)
    DllStructSetData($st_TVITEM, 2, $h_item)
    $iOrigPtr = DllStructGetPtr($st_text)
    DllStructSetData($st_TVITEM, 5, $iOrigPtr)
    DllStructSetData($st_TVITEM, 6, DllStructGetSize($st_text))
    
    If GUICtrlSendMsg($i_treeview, $TVM_GETITEM, 0, DllStructGetPtr($st_TVITEM)) Then
        $iNewPtr = DllStructGetData($st_TVItem, 5)
        If ($iOrigPtr <> $iNewPtr) Then
        ; The text was placed in a different buffer
            ConsoleWrite("GUITreeViewGetText2: new pointer" & @CRLF)  ; debug
            $st_text = 0
            $st_text = DllStructCreate("char[" & DllStructGetData($st_TVITEM, 6) & "]", $iNewPtr)
        EndIf
        $s_text = DllStructGetData($st_text, 1)
    EndIf  ; sendMsg
    
    Return $s_text
EndFunc  ;==>_GUICtrlTreeViewGetText2
Link to comment
Share on other sites

Maybe this is along the lines of what your trying to accomplish?

#include <GuiConstants.au3>

Global $gaNodes[9][3] = [[0, "item 1", "This is Item 1"], [0, "item 2", "This is Item 2"], [1, "item 2.1", "This is Item 2.1"], [1, "item 2.2", "This is Item 2.2"], _
[2, "item 2.2.1", "This is Item 2.2.1"],[0, "item 3", "Oh boy, Item 3"], [1, "item 3.1", "Hello from 3.1"], _
[1, "item 3.2", "3.2 Here"],[0, "item 4", "Last but maybe least: Item 4"]]
Global $ghForm2, $IDC_TREEVIEW, $IDC_InfoInput, $IDC_InfoBtn, $gaTV_Items[2],$gaParents[1]
$gaTV_Items[0] = 0
$ghForm2 = GUICreate("CHM to Text - Choose starting node", 342, 183, 341, 278, -1798701056, 256)
$IDC_TREEVIEWLABEL = GUICtrlCreateLabel("Choose Node", 14, 14, 65, 16, 1342308608, 0)
$IDC_TREEVIEW = GUICtrlCreateTreeView(85, 14, 200, 100, 1350631424, 0)
$IDC_InfoInput = GUICtrlCreateInput("", 14, 128, 200, 16, 1342374016, 0)
$IDC_InfoBtn = GUICtrlCreateButton("Info", 222, 128, 100, 16, 1342246656, 0)
GUISetState(@SW_SHOW)

BuildTreeView($IDC_TREEVIEW, $gaNodes)

While 1 
    $nMsg = GUIGetMsg()
    Switch $nMsg
        Case $GUI_EVENT_CLOSE
            Exit
        Case $IDC_InfoBtn
            Local $iTV_Clicked = GUICtrlRead($IDC_TREEVIEW)
            ConsoleWrite("CtrlRead returned Selected item is " & $iTV_Clicked& @CRLF)
            $iItem = GetTVIndex($iTV_Clicked)
            If $iItem >= 0 Then
                GuiCtrlSetData($IDC_InfoInput,$gaNodes[$iItem][2])
            EndIf
    EndSwitch
    Sleep(10)
WEnd
    
Func BuildTreeView($nTreeView,$aNodes)
    Local $iParent =0
    Local $iLastLevel = 0
    Local $iCurrent =0
    Local $nNode = 0
    For $x = 0 to UBound($aNodes,1)-1
    $iCurrent = $aNodes[$x][0]
        Switch $iCurrent
            Case 0
                $nNode = GUICtrlCreateTreeViewItem($aNodes[$x][1],$nTreeView)
                Push($iCurrent,$nNode)
                $iParent =$nNode
            Case Else
                If $iParent = 0 Then
                    $iParent = $nTreeView
                Else
                    $iParent = Peek($iCurrent-1)
                    If $iParent > 0 Then
                        $nNode = GUICtrlCreateTreeViewItem($aNodes[$x][1],$iParent)
                        Push($iCurrent,$nNode)
                    EndIf
                EndIf
        EndSwitch
        $iLast = $aNodes[$x][0]
        If $nNode <> 0 Then
            If $gaTV_Items[0]+1 = UBound($gaTV_Items) Then
                ReDim $gaTV_Items[UBound($gaTV_Items)+10]
            EndIf
            $gaTV_Items[$gaTV_Items[0]+1] = $nNode
            $gaTV_Items[0] += 1
        EndIf
    Next
EndFunc

Func GetTVIndex($iTV_Clicked)
    Local $retval = -1
    For $x = 1 to $gaTV_Items[0]
        If $gaTV_Items[$x] = $iTV_Clicked Then
            $retval = $x-1
            ExitLoop
        EndIf
    Next
    Return $retval
EndFunc

Func Peek($iLevel)
    If $iLevel < 0 Then
        Return SetError(1,0,0)
    EndIf
    If $iLevel < UBound($gaParents) Then
        Return $gaParents[$iLevel]
    EndIf
    Return -1
EndFunc

Func Push($iLevel,$iNode)
    If $iLevel < 0 Then
        Return SetError(1,0,0)
    EndIf
    If $iNode < 0 Then
        Return SetError(2,0,0)
    EndIf
    Select
        Case $iLevel < UBound($gaParents)
            $gaParents[$iLevel] = $iNode
        Case $iLevel = UBound($gaParents)
            ReDim $gaParents[$iLevel +1]
            $gaParents[$iLevel] = $iNode
        Case Else
            Return SetError(1,0,0)
    EndSelect
    ;ConsoleWrite(StringFormat("Level: %d Node: %d\n",$iLevel,$iNode))    
    Return 1
EndFunc

Edit: Removed an un-needed line.

Edited by eltorro
Link to comment
Share on other sites

I was also looking for a method to store additional data in treeviews a long while back, but gave up. With the recent addition of ControlTreeView() in the betas, though, I've found that I could simply create an array in parallel with the treeview, then use ControlTreeView()'s "GetSelected" command to grab the index of the selected item, then look it up in the array. Treeview items with child nodes can be represented by nested arrays (I guess with the 0 element of that nested array containing data for that particular item that has children?). You'll need to remember to increment each index returned by ControlTreeView() by 1, but it can be easily done when you're parsing the ControlTreeView().

TreeItem1
TreeItem2
    TreeItem2Sub1
    TreeItem2Sub2
TreeItem3

would maybe have an accompanying array of

[
    0,
    "Data associated with TreeItem1",
    [
        "Data associated with TreeItem2",
        "Data associated with TreeItem2Sub1",
        "Data associated with TreeItem2Sub2"
    ],
    "Data associated with TreeItem3"
]

The 0 is there since it would be easier to just increment every returned index by 1 instead of having to remember that the first index returned by ControlTreeView() doesn't need to be incremented.

I'm not a big fan of this method, as it feels "unclean" to me, but it got the job done for me anyway, so I thought I'd just throw the idea out there in case you weren't aware of it :)

Edited by -Ultima-

[ WinINet.au3 | Array.au3 (Optimized) | _UnixTimeParse() ]

Link to comment
Share on other sites

Thanks for your responses. I used something like eltorro's solution. I was not able to retrieve a number for the currently-focused item matching the number stored by _GUICtrlTreeViewInsertItem2 (should be the same as GUICtrlTreeViewInsertItem in the GUITreeView.au3 library), either by GUICtrlRead or by GUICtrlGetHandle(GUICtrlRead()). I replaced my _GUICtrlTreeViewInsertItem2 with GUICtrlCreateTreeViewItem and it worked. Because I forgot to disable my debug call to my _GUICtrlTreeViewGetParam function I found out that GUICtrlCreateTreeViewItem does store the item ID in the LPARAM value of the item structure. I also found out that the problem with my code for that is in the storing function (_GUICtrlTreeViewInsertItem2) since _GUICtrlTreeViewGetParam seems to work.

Thanks again.

GaryC

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...