Announcement

Collapse
No announcement yet.

TreeVew Walk-Through in 20 Lines of Code

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Michael Mattias
    replied
    example Mattias gave, my code requires that the entire structure must be walked before being able to use the results. With yours, the walk-through follows example 2, gets immediate results, and can be stopped at any point.
    So you call the enumeration twice, either to two diffferent callback functions or using a different "DwUserData" value known to your application.

    BTW, if you return false from your callback function, the recursive enumeration stops and unrolls everything.

    The recursive version is also not constrained to be "hard-coded" to support a finite 'depth limit.'


    MCM

    Leave a comment:


  • Christopher Carroll
    replied
    Gary

    I have not had occasion to list out files and directories. However, this code from Stuart Sanders is quite useful:
    Treeview/Listview Select Files and Folders

    Listing directories is reasonably fast using his code, but the loading of a large number of files within a directory is a bit slow.

    The display (of the files in the treeview) is separate from the parsing of said files and directories. Depending on what is required, I would probably read the list of the directories (in chunks?) into a structure in memory, then dump these as a group into the treeview. And then do the same for the files in a particular directory only when that directory is selected in the treeview. The files listed could be cached, or even pre-read using threads (i.e. if a directory is selected, read the files in that directory and pre-read the files in the next n sub-directories.

    For an example of using pidls, see this posting by Edwin Knoppert:
    Enum a folder the com way

    The code I posted above is suitable to search for text (i.e. a file name) within the treeview once the data are loaded to it. Just do a string comparison in section E. If a match is made, the code can be exited at that stage as you have noted.

    Christopher

    Leave a comment:


  • Gary Beene
    replied
    Christopher,

    Thanks for the code.

    I inserted a highlight/sleep line and it walked through perfectly.

    Code:
                ttviex.hItem = hItem
                  Treeview Select hDlg, 100, hitem
                  Sleep 400
                ttviex.mask = %TVIF_TEXT Or %TVIF_PARA
    Hating to re-invent the wheel, can I ask if you also have a version of this code that works with listing subdirectories? If not, I'll give the conversion a try.

    A "problem" with my code is that in the 2nd example Mattias gave, my code requires that the entire structure must be walked before being able to use the results. With yours, the walk-through follows example 2, gets immediate results, and can be stopped at any point.

    For TreeView controls, it's not an issue because reading a Tree takes almost no time at all (tree structures aren't usually that large). But with subfolder counts of >10K, not having to finish reading the entire structure can be an advantage if you're searching for that is found high in the structure.

    In an earlier post about listing subdirectories, Edwin Knoppert noted that pidls (which I know nothing about - yet), are the fastest way to go. If you have used you code for subdirectories, how did it fare on speed?

    Leave a comment:


  • Gary Beene
    replied
    Yep, good point. Luckily for me, the code I posted gives both answers.

    The data array, unsorted, matches your first example. Sorted, it matches your second example.

    Cool beans!

    Leave a comment:


  • Michael Mattias
    replied
    Don't forget, there are multiple "correct" ways to walk a tree...

    Could be...
    Code:
     1   
        4 
        5
            10 
            11
            12 
     2 
        6 
        7
            13
            14 
            15
     3
        8
        9
    Then again, you might want ..
    Code:
    1
      2
         3
         4 
         5
      6 
         7
         8 
         9 
     10 
         11
            12
            13
    MCM

    Leave a comment:


  • Christopher Carroll
    replied
    Here is an example without recursion or arrays:

    Code:
    Function TreeWalk(ByVal vhTreeMenu As Dword) As Long
    ' Purpose:       example to traverse a treeview control without using recursion
    ' Parameters:    vhTreeMenu - handle to treeview control
    ' Example:       Call TreeWalk(GetDlgItem(hWnd, %CTL_TREEVIEW))
        Local hItem As Dword                ' handle to current item
        Local hPrev As Dword                ' handle to previous item
        Local lngDepth As Long              ' count of current depth
        Local lngUp As Long                 ' flag to determine if time to go up a level
        Local phItem As Dword Ptr           ' pointer to array used to track treeview items
        Local ptnmtv As NM_TREEVIEW Ptr     ' treeview notification message information
        Local szText As Asciiz * %MAX_PATH  ' working value
        Local ttviex As TVITEMEX            ' specifies or receives the attributes of a treeview item
    
        ' allocate memory for the array used to track treeview items (tip via Dominic Mitchell)
        ' 256 * 4 allows for a depth of 256. Modify as needed. Test if depth approaches this if desired.
        phItem = HeapAlloc(GetProcessHeap(), %HEAP_ZERO_MEMORY, 256 * 4)
        If IsFalse(phItem) Then
            ' could not allocate memory
            GoTo TreeWalk_Exit
        End If
        ' get root item
        hItem = SendMessage(vhTreeMenu, %TVM_GETNEXTITEM, %TVGN_ROOT, %NULL)
        If hItem <> 0 Then
            @phItem[0] = hItem
            hPrev = hItem
        Else
            ' no root defined
            GoTo TreeWalk_Exit
        End If
        While lngDepth >= 0
            If lngUp Then
                ' A: just came back up a level
                lngUp = %FALSE
                hItem = SendMessage(vhTreeMenu, %TVM_GETNEXTITEM, %TVGN_NEXT, hPrev)
            Else
                ' B: test if item has a child. if not, then test if there is a sibling
                hItem = SendMessage(vhTreeMenu, %TVM_GETNEXTITEM, %TVGN_CHILD, hPrev)
                If hItem <> 0 Then
                    ' C: go down 1 level
                    Incr lngDepth
                Else
                    ' D: check for siblings on this level
                    hItem = SendMessage(vhTreeMenu, %TVM_GETNEXTITEM, %TVGN_NEXT, hPrev)
                End If
            End If
            If hItem <> 0 Then
                ' E: found a sibling, either on same level [D], or after coming back up a level [A]
                ' do something. i.e. get the text of the current item:
                ttviex.hItem = hItem
                ttviex.mask = %TVIF_TEXT Or %TVIF_PARAM
                ttviex.cchTextMax = SizeOf(szText)
                ttviex.pszText = VarPtr(szText)
                SendMessage vhTreeMenu, %TVM_GETITEM, 0, ByVal VarPtr(ttviex)
                ' continue
                @phItem[lngDepth] = hItem
                hPrev = hItem
            Else
                ' no more siblings at this level
                Decr lngDepth
                If lngDepth >= 0 Then
                    hPrev = @phItem[lngDepth]
                    lngUp = %TRUE
                Else
                    ' back at top level, so exit
                    Exit Loop
                End If
            End If
        Wend
    TreeWalk_Exit:
        ' release memory
        HeapFree GetProcessHeap(), 0, phItem
    End Function
    Last edited by Christopher Carroll; 19 Feb 2009, 12:33 AM. Reason: Tidied up indentation

    Leave a comment:


  • Michael Mattias
    replied
    Code:
    ' =====================================================================================
    ' actual recursing function; returns 0 = Enum Completed, 1 = Enum was terminated in callback
    ' This function enumerates the tree whose root is hStartItem. Includes hStartItem
    ' ***************************************************************************
    ' THIS FUNCTION SHOULD NOT BE CALLED BY THE USER, CALL TV_ENUMTREE or TV_ENUMTREE_SIBLINGS Instead
    ' ***************************************************************************
    FUNCTION Treeview_Enum_Recursing_Function (hTV AS LONG, hStartItem AS LONG, BYVAL CbAddress AS DWORD, _
              BYVAL dwUserData AS LONG, BYVAL doSiblings AS LONG, BYVAL ResetStop AS LONG ) AS LONG
    
        LOCAL hItem AS LONG, hChild AS LONG, hSibling AS LONG, tvi AS TV_ITEM
        LOCAL DoSibs AS LONG
        LOCAL szText AS ASCIIZ * %TV_ENUM_MAX_TEXT_LEN
    
        LOCAL  Continue AS LONG
        STATIC StopEnum AS LONG   ' yes, STATIC, so we can exit multiple levels of recursion
    
        IF ResetStop THEN         ' reset stop is set on each "fresh" enumeration
          StopEnum = %FALSE
        END IF
        IF StopEnum THEN    ' user forced a stop in callback (recursions are called with resetStop=false)
           FUNCTION = 1
           EXIT FUNCTION
        END IF
        ' get stuff into LOCAL vars to make sure each recursion gets own copy
        hItem      = hStartItem
        DoSibs     = DoSiblings  ' for first time, will be false, so will not do siblings; when
                                 ' called from restart, doSibs WILL be true!
        DO WHILE hItem
          ' get the tvi for this item and send it to the callback function
          tvi.hItem       = hItem    ' we want this one
          tvi.mask        = %TV_ENUM_MASK
          tvi.cchTextMax  = SIZEOF(szText)
          tvi.pszText     = VARPTR(szText)
          TreeView_GetItem hTv, BYVAL VARPTR(tvi)
          ' send tvi to the USER callback function
          CALL DWORD cbAddress _
            USING TV_Enum_User_callback  _
               (dwUserData, tvi, htv) TO Continue
              ' TRACE PRINT "Callback returns continue=" & STR$(Continue)
               IF ISFALSE Continue THEN
                   StopEnum          = %TRUE     ' set the static stop flag which will be read
                                                 ' as function recurses out of itself.
                   EXIT DO
               END IF
          ' if any children of this node, call this function recursively.
          hChild = TreeView_GetChild(hTV,hItem)
          IF hChild THEN
              ' The GetChild macro returns the first child of hItem in hTv
              ' call ourself, but now the child is the starting item and we do siblings and do NOT reset the Stop flag
               CALL Treeview_Enum_Recursing_Function (hTv, hChild, CbAddress, dwUserData, %TRUE, %FALSE)
          END IF
          IF StopEnum THEN
              EXIT DO
          END IF
    
          ' and make hitem the next sibling at this level, if doing siblings
          IF DoSibs THEN
              hItem = TreeView_GetNextSibling (hTv, hItem)
          ELSE
              hItem = %NULL
          END IF
        LOOP
        ' if we get here, enum completed without user action
        FUNCTION = 0
    
    END FUNCTION
    
    ' ===============================================================================
    '                USER-CALLED TREE ENUMERATION FUNCTIONS
    ' Treeview_enumtree: Enumerate the nodes of the tree whose root is at hStartItem
    '                NOTE: does NOT do siblings of hStartItem!
    
    '================================================================================
    FUNCTION TreeView_EnumTree (hTV AS LONG, hStartItem AS LONG, BYVAL CbAddress AS DWORD, BYVAL dwUserData AS LONG) AS LONG
      ' here we just start the recursion, FALSE, TRUE ==> NO siblings, YES reset stop flag
       LOCAL I AS LONG
       CALL TreeView_Enum_Recursing_Function _
            (hTv, hStartItem, CbAddress, dwUserData, %TV_ENUM_SIBLINGS_NO, %TRUE) TO I
       FUNCTION = I
    END FUNCTION
    MCM

    Leave a comment:


  • Joe Byrne
    replied
    Ditto for code. I'm willing to take (possibly) useful code and make minor modifications to it to suit my programming needs. If someone offers me free beer, I don't usually turn it down because it's not in a glass of my liking.
    Couldn't agree with you more.

    IMO, posted code is mainly for example purposes. Unless specifically stated as "complete application, no modifications required or allowed" one should always treat 'free' code as a good (usually) example of how to do something. Taking anything verbatim and 'forcing' it to fit someplace is a recipe for disaster in my book.

    What's the old adage about looking a gift horse in the mouth?

    BTW, I don't drink adult beverages either, but I completely understood your analogy

    Leave a comment:


  • Joe Byrne
    replied
    The source code forum is mainly for working code that people might find useful. Complete apps, small functions, whatever. Code posted within other forums are usually "why does this (not) work", "what am I missing", etc samples rather than code being submitted to the general public for use.

    Most of the time, people will create a thread in one of the forums like "Discussions about (code) posted in the source code forum" with a link to the actual code posting in the message body. This way, people can discuss it without "clogging" the source code forum with lots of discussion, and at the same time, the code can be easily searched on without having to search ever conceivable forum to find that "I know I saw something like this someplace"....

    There are no 'hard and fast' rules that will get you banished for breaking, but the more we stick to some kind of organization, the better it is for everyone.

    I'd also suggest Michael's recommendation of putting the compiler version and (if appropriate) Win32inc date at the top along with any other information that might help someone 5 years from now.

    Leave a comment:


  • Gary Beene
    replied
    James,

    And a rant it was!

    I understand, and share, your opinion on having code available in a reusable format. Having code available in a format that requires the least modification for future reuse is efficient, if nothing else. Better yet, no mods means no introduced mistakes! It's the best way to benefit from having code snippets available, as well as to maximize the ease of sharing code between peers.

    However, your no-exceptions philosophy is somewhat limiting. If someone gives you free peanut butter and bread, you ought to be willing to put them in a form you can eat more easily.

    Ditto for code. I'm willing to take (possibly) useful code and make minor modifications to it to suit my programming needs. If someone offers me free beer, I don't usually turn it down because it's not in a glass of my liking.

    (actually, I don't drink. the beer was just an analogy to get my point across)

    Leave a comment:


  • Gary Beene
    replied
    Joe,
    Yes, you bring out an ongoing dilemma. On a couple of posts, I've had to think about where to post. I've put code in both places, depending on my intent.

    The forum descriptions say that the Source Code forum is for posting, no discussion allowed. I was interested in feedback, so here seemed more appropriate. Is there an "understood" policy within the folks here that code goes to the Source Code forum regardless?

    Mostly, I see folks post code asking "Why does it work". I would personally like to see code posted with discussion about why it works, what it's shortcomings are, why one approach is better than another - specific to PBWin9. That's more along the lines of the posts I made.

    And I'm assuming double-posting is inappropriate.

    Leave a comment:


  • Joe Byrne
    replied
    I would also recommend posting source code samples in the source code forum. While you're free to post anyplace, it makes finding source code easier if its located in the forum most people think of as containing source code

    Leave a comment:


  • jcfuller
    replied
    Gary,
    While your code may be excellent I'll never know as I won't even look at a piece of code with more than a couple of globals. The use of globals makes it unusable as a library routine. It appears you have some coding talent but it needs a little more focus on reusable code in my opinion.
    This is NOT a rant on GLOBALS per se it's about code reuse!

    James

    Leave a comment:


  • Gary Beene
    started a topic TreeVew Walk-Through in 20 Lines of Code

    TreeVew Walk-Through in 20 Lines of Code

    The non-recursive algorithm I posted yesterday for listing subdirectories can also be used to walk through a TreeView in just about 20 lines of code - pretty short as compared to most examples I've seen on the forum.

    Here's the basic algorithm. The four basic arrays used are Items(), ItemsP(), ItemsS() and ItemsL() - handles, position, descriptions and levels of the nodes in the TreeView control.

    Code:
    Sub SearchTreeView(hTmp As Dword, ItemsCount As Long)
        Dim iPOS As Long, iLevel As Long, iOrder As Long
        ItemsL(ItemsCount) = iLevel :  Items(ItemsCount) = hTmp
    
        '1st item (starting point)
        Treeview Get Text hDlg, 100, hTmp To ItemsS(ItemsCount)
        ItemsP(ItemsCount) = "000000000000"     'use "000" for each level (1000 items max per level)
        ItemsS(ItemsCount) = ItemsP(ItemsCount) + Str$(ItemsL(ItemsCount)) + " " + ItemsS(ItemsCount)
    
        'cycles through all items, put various info in arrays
        While Items(iPOS)
            iOrder = 0
            Treeview Get Child hDlg, 100, Items(iPOS) To hTmp
            If hTmp Then iLevel = ItemsL(iPOS) + 1
            While hTmp
                Incr iOrder :  Incr ItemsCount
                Items(ItemsCount) =  hTmp
                ItemsL(ItemsCount) = iLevel
                ItemsP(ItemsCount) = ItemsP(iPOS)
                Mid$(ItemsP(ItemsCount),(iLevel-1)*3+1,3) =  Format$(iOrder,"000")
                Treeview Get Text hDlg, 100, hTMP To ItemsS(ItemsCount)
                ItemsS(ItemsCount) = ItemsP(ItemsCount) + Str$(ItemsL(ItemsCount)) + " " + ItemsS(ItemsCount)
                Treeview Get Next hDlg, 100, hTmp To hTmp
            Wend
            Incr iPOS
        Wend
        'sort the ItemsS array (tag Items along with it)
        ReDim Preserve ItemsS(ItemsCount), Items(ItemsCount)
        Array Sort ItemsS(), TagArray Items()
    End Sub
    With TreeView structures, which in my experience are relative small (a few hundreds of items), this algorithm work very fast. You may recall from the earlier posting on the algorithm that reading very large subdirectory structures (10K items) doesn't give an "instant" response. Also, since TreeView structures are in memory, the speed is especially enhanced over reading subdirectories from a disk drive. In my test, it scanned over 5K items as quickly as I could press the button - no noticeable delay.

    Another good thing about using the algorithm with a TreeView is that the maximum number of items is known, answering the question of how large to make the arrays that contain the results.

    And here's a complete example app using the algorithm. A short TreeView is created, the tree scanned, and then each node highlighted sequentially just to show that it works. An array with the node handles and an array with a text description of each node are the basic output.

    Code:
     #Compile Exe
     #Resource "pb-test.pbr"
     Global hDlg As Dword, hLst As Dword, hRoot As Dword, hItem As Dword, hTmp As Dword
     Global Items() As Dword, ItemsS() As String, ItemsP() As String, ItemsL() As Long
     Function PBMain() As Long
          Dialog New Pixels, 0, "TreeView Test",300,100,330,230, _
                                          %WS_OverlappedWindow, 0 To hDlg
        'create control
        Control Add Treeview, hDlg, 100, "", 10,40,150,175
        Control Add Button, hDlg, 101,"Start Search", 10,10,100,20
        Control Add Label, hDlg, 150,"<count>", 120,10,100,20, %WS_Border Or %SS_Center
    
        'create imagelist  w,h,depth,size
        ImageList New Icon 16,16,32,3 To hLst
        ImageList Add Icon hLst, "x"
        ImageList Add Icon hLst, "y"
        ImageList Add Icon hLst, "z"
        ImageList Add Icon hLst, "check"
    
        'attach imagelist
        Treeview Set ImageList hdlg, 100, hLst
    
        'add items  hdlg,id&,hPrnt,hIAftr,image&,selimage&,txt$ To hItem
        Treeview Insert Item hDlg,100,0,%TVI_Last,0,0,"Home" To hRoot
    
        Treeview Insert Item hDlg,100,hRoot,%TVI_Last,1,1,"One" To hTmp
            Treeview Insert Item hDlg,100,hTmp,%TVI_Last,1,1,"One-1" To hItem
            Treeview Insert Item hDlg,100,hTmp,%TVI_Last,2,2,"One-2" To hItem
    
        Treeview Insert Item hDlg,100,hRoot,%TVI_Last,2,2,"Two" To hTmp
            Treeview Insert Item hDlg,100,hTmp,%TVI_Last,1,1,"Two-1" To hItem
            Treeview Insert Item hDlg,100,hTmp,%TVI_Last,2,2,"Two-2" To hItem
    
        Treeview Insert Item hDlg,100,hRoot,%TVI_Last,3,3,"Three" To hTmp
            Treeview Insert Item hDlg,100,hTmp,%TVI_Last,1,1,"Three-1" To hItem
            Treeview Insert Item hDlg,100,hTmp,%TVI_Last,2,2,"Three-2" To hItem
    
        Dialog Show Modal hDlg Call DlgProc
     End Function
    
     CallBack Function DlgProc() As Long
          If Cb.Msg = %WM_Command And Cb.Ctl = 101 And Cb.CtlMsg = %BN_Clicked Then
              'initialize variables and dimension arrays
              Dim iCount As Long, hSelected As Dword, ItemsCount As Long
              Treeview Get Count hDlg, 100 To iCount   'max items is no more than entire tree
              ReDim Items(iCount), ItemsS(iCount), ItemsP(iCount), ItemsL(iCount)
              'Items-handles, ItemsS-sort string, ItemP-position string, ItemsL-level
    
              'get the Tree data
              Treeview Get Select hDlg, 100 To hSelected
              SearchTreeView(hSelected, ItemsCount)
    
              'Remove empty elements, sort and display the array ItemsS()
              Control Add ListBox, hDlg, 200,ItemsS(), 170,40,150,175
              Control Set Text hDlg, 150, Str$(ItemsCount+1)
              Dialog ReDraw hDlg
    
              'walk through tree from starting point, highlighting each item
              For iCount = 0 To UBound(Items)
                  Treeview Select hDlg, 100, Items(iCount)
                  Sleep 400
              Next i
    
          End If
     End Function
    
    Sub SearchTreeView(hTmp As Dword, ItemsCount As Long)
        Dim iPOS As Long, iLevel As Long, iOrder As Long
        ItemsL(ItemsCount) = iLevel :  Items(ItemsCount) = hTmp
    
        '1st item (starting point)
        Treeview Get Text hDlg, 100, hTmp To ItemsS(ItemsCount)
        ItemsP(ItemsCount) = "000000000000000"     'use "000" for each level (1000 items max per level)
        ItemsS(ItemsCount) = ItemsP(ItemsCount) + Str$(ItemsL(ItemsCount)) + " " + ItemsS(ItemsCount)
    
        'cycles through all items, put various info in arrays
        While Items(iPOS)
            iOrder = 0
            Treeview Get Child hDlg, 100, Items(iPOS) To hTmp
            If hTmp Then iLevel = ItemsL(iPOS) + 1
            While hTmp
                Incr iOrder :  Incr ItemsCount
                Items(ItemsCount) =  hTmp
                ItemsL(ItemsCount) = iLevel
                ItemsP(ItemsCount) = ItemsP(iPOS)
                Mid$(ItemsP(ItemsCount),(iLevel-1)*3+1,3) =  Format$(iOrder,"000")
                Treeview Get Text hDlg, 100, hTMP To ItemsS(ItemsCount)
                ItemsS(ItemsCount) = ItemsP(ItemsCount) + Str$(ItemsL(ItemsCount)) + " " + ItemsS(ItemsCount)
                Treeview Get Next hDlg, 100, hTmp To hTmp
            Wend
            Incr iPOS
        Wend
        'sort the ItemsS array (tag Items along with it)
        ReDim Preserve ItemsS(ItemsCount), Items(ItemsCount)
        Array Sort ItemsS(), TagArray Items()
    End Sub
    This example is set up to handle a 5-level Tree and 1000 children per item. Just extend the "00000..." string by groups of 3 zero's to support more levels.
    Last edited by Gary Beene; 18 Feb 2009, 08:44 AM.
Working...
X
😀
🥰
🤢
😎
😡
👍
👎