Announcement

Collapse
No announcement yet.

A treeview Question for Charles Dietz

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

  • A treeview Question for Charles Dietz

    Thanks for your treeview starter code. it's awsome.
    I have modified the program you posted in 2007 to show the files when
    clicking the expanded directories. With some gleaning from tonny bjorn, i was
    able to get the files from the directories that were not expandable which
    i wasnt getting to begin with. The only trouble i have is when you expand/contract a folder more than once, it doubles or triples the subfolder
    names. Any way to correct this ? Also, how to get the path for the selected file.
    Here is the modified code - It's pretty much as posted except for an additional textbox and button.
    I changed the textbox multiline to a listbox which is easier to get the file name.
    Code:
    #COMPILE EXE
    #DIM ALL
    #INCLUDE "win32api.inc"
    #INCLUDE "Commctrl.inc"
    
    'Include file statements
    '========================================================================
    %ID_TREE = 100
    
    TYPE itemRef_UDT
       hItem   AS LONG
       parent  AS LONG
       subDirs AS BYTE
       done    AS BYTE
    END TYPE
    
    GLOBAL hTree AS LONG
    GLOBAL numItems AS LONG
    GLOBAL maxDim AS LONG
    GLOBAL item() AS STRING
    GLOBAL itemRef() AS itemRef_UDT
    GLOBAL UserSelected$
    
    DECLARE FUNCTION TVInsertItem(LONG, LONG, STRING, BYTE) AS LONG
    DECLARE FUNCTION getItemNo(LONG) AS LONG
    DECLARE FUNCTION subDirsExist(STRING) AS BYTE
    DECLARE SUB getNextDirs(LONG)
    '========================================================================
    
    FUNCTION PBMAIN()
       LOCAL hMain AS LONG
       LOCAL dummy$()
       DIM dummy$(0)
       DIALOG NEW PIXELS, 0, "my TreeView", , , 500, 400,%WS_SYSMENU OR %WS_MINIMIZEBOX TO hMain
        'add treeview common control
       CONTROL ADD "SysTreeView32", hMain, %ID_TREE, "", 0, 0, 300, 300, _
                  %WS_CHILD OR %WS_VISIBLE OR %TVS_HASBUTTONS OR %TVS_HASLINES OR _
                  %TVS_LINESATROOT OR %TVS_SHOWSELALWAYS, %WS_EX_CLIENTEDGE
       CONTROL ADD BUTTON, hMain,201,"&Close",100,320,80,30
       CONTROL ADD BUTTON, hMain,202,"&OK",100,360,80,30
       CONTROL ADD LISTBOX, hMain,301,dummy$(),310,0,170,300,%WS_VSCROLL 'OR %ES_MULTILINE
       CONTROL ADD TEXTBOX, hMain,302,"",310,320,120,22',%ES_MULTILINE OR %WS_VSCROLL
       DIALOG SHOW MODAL hMain CALL TreeViewProc
    MSGBOX "File is "+userSelected$
       END FUNCTION
    
    CALLBACK FUNCTION TreeViewProc()
       LOCAL i, m, n, hTreeItem, hItem, hParent, hChild AS LONG
       LOCAL s AS STRING, szTxt AS ASCIIZ*64
       LOCAL lpNmh AS NMHDR PTR
       LOCAL lpTV AS NM_TREEVIEW PTR
       LOCAL treeItem AS TV_ITEM
       LOCAL TVdi AS TV_DISPINFO
       LOCAL txt$,lResult$
       LOCAL dirtosearch$
       LOCAL dummy$()
       LOCAL path$
    
       DIM dummy$(0)
       SELECT CASE CBMSG
          CASE %WM_INITDIALOG
             maxDim = 2000
             DIM item(maxDim)
             DIM itemRef(maxDim)
    
             'find the first level children of C:\
             item(0) = "C:": getNextDirs 0
    
             'display the folders and files
             CONTROL HANDLE CBHNDL, %ID_TREE TO hTree
             itemRef(0).hItem = TVInsertItem(hTree, 0, "C:\", 1)
             FOR i = 1 TO numItems
                s = PARSE$(item(i), "\", -1)
                hParent = itemRef(itemRef(i).parent).hItem
                hChild = TVInsertItem(hTree, hParent, s, itemRef(i).subDirs)
                itemRef(i).hItem = hChild
             NEXT i
    
             'open with first level folders... the children of C:\
             SendMessage hTree, %TVM_EXPAND, %TVE_EXPAND, itemRef(0).hItem
          CASE %WM_PAINT
          CASE %WM_COMMAND
             SELECT CASE CBCTL
               CASE 201 'close button
                 UserSelected$=""
                 DIALOG END CBHNDL,0
               CASE 202 'ok
                 DIALOG END CBHNDL,1
               CASE 301 'select a file from textbox
                 LISTBOX GET TEXT CBHNDL,301 TO UserSelected$
                 CONTROL SET TEXT CBHNDL,302,UserSelected$
               CASE 302 'file name text box
                 CONTROL GET TEXT CBHNDL,302 TO UserSelected$
             END SELECT
          CASE %WM_NOTIFY
             lpNmh = CBLPARAM
             SELECT CASE @lpNmh.Code
                CASE %TVN_ITEMEXPANDING
                   'expand or collapse the treeview node hTreeItem
                   lpTV = CBLPARAM
                   hTreeItem = @lpTV.ItemNew.hItem
                   m = numItems: n = getItemNo(hTreeItem)
                   IF itemRef(n).done THEN EXIT FUNCTION
                   getNextDirs n
                   FOR i = m + 1 TO numItems
                      s = PARSE$(item(i), "\", -1)
                      hParent = itemRef(itemRef(i).parent).hItem
                      hChild = TVInsertItem(hTree, hParent, s, itemRef(i).subDirs)
                      itemRef(i).hItem = hChild
                   NEXT i
    '
                   txt$=""
                   LISTBOX RESET CBHNDL,301
                   dirtosearch$=item(n)
                   lResult$=DIR$(dirtosearch$+"\*.*",0)
                   WHILE lresult$<>""
                     txt$ =LResult$ 'txt$+lResult$+$CRLF
                     LISTBOX ADD CBHNDL,301,txt$
                     lResult$=DIR$
                   WEND
                CASE %TVN_SELCHANGING 'LAST 'FIRST 'KEYDOWN
                   lpTV = CBLPARAM
                   hTreeItem = @lpTV.ItemNew.hItem
                   m = numItems: n = getItemNo(hTreeItem)
                   txt$=""
                   LISTBOX RESET CBHNDL,301
                   dirtosearch$=item(n)
                   lResult$=DIR$(dirtosearch$+"\*.*",0)
                   WHILE lresult$<>""
                     txt$ =lrEsult$ 'txt$+lResult$+$CRLF
                     LISTBOX ADD CBHNDL,301,txt$
                     'count&=count&+1
                     lResult$=DIR$
                   WEND
                  
             END SELECT
             
          CASE %WM_SYSCOMMAND
          CASE %WM_DESTROY
       END SELECT
    END FUNCTION
    
    FUNCTION getItemNo(hTreeItem AS LONG) AS LONG
       'get itemNo corresponding to item's handle
       'hTreeItem  = item handle
       'numItems   = number of items defined (nodes of treeview)
       '===================================================================
       LOCAL i, n AS LONG
       FOR i = 1 TO numItems
          IF itemRef(i).hItem = hTreeItem THEN n = i: EXIT FOR
       NEXT i
       FUNCTION = n
    END FUNCTION
    
    FUNCTION TVInsertItem(hTree AS LONG, hParent AS LONG, sTxt AS STRING, b AS BYTE) AS LONG
       'insert item into treeview control
       '--------------------------------------------------------------------------------
       'hTree   = handle of treeview control
       'hParent = handle of parent to item
       'sTxt    = string label identifying item
       'b       = set if children exist for this item
       '================================================================================
       LOCAL tv_insert AS TV_INSERTSTRUCT
       tv_insert.hParent              = hParent
       tv_insert.Item.Item.mask       = %TVIF_TEXT OR %TVIF_CHILDREN
       tv_insert.Item.Item.pszText    = STRPTR(sTxt)
       tv_insert.Item.Item.cchTextMax = LEN(sTxt)
       IF b THEN tv_insert.Item.Item.cChildren = 1
       FUNCTION = TreeView_InsertItem(hTree, tv_insert)
    END FUNCTION
    
    SUB getNextDirs(itemNo AS LONG)
       'find next first level children of itemNo
       'update global item(), itemRef(), and numItems
       '-------------------------------------------------------------------
       'item()    = array of folder names
       'itemRef() = array of folder info UDTs
       'numItems  = number of nodes identified so far
       '===================================================================
       LOCAL s, sParent AS STRING
       LOCAL attr, i, k, n, nDim AS LONG
       DIM sTemp(maxDim) AS STRING
       sParent = RTRIM$(item(itemNo), "\") + "\"
       attr = %SUBDIR 'OR %READONLY OR %HIDDEN OR %SYSTEM
       s = DIR$(sParent, attr)
       IF LEN(s) THEN
          k = GETATTR(sParent + s) AND %SUBDIR
          IF k THEN INCR n: sTemp(n) = s
          DO
             s = DIR$: k = 0: IF LEN(s) = 0 THEN EXIT DO
             k = GETATTR(sParent + s) AND %SUBDIR
             IF k THEN INCR n: sTemp(n) = s
          LOOP
       END IF
       itemRef(numItems).done = 1
       'found n subdirectories of itemNo
       '... now update numItems, item(), and itemRef()
       IF n = 0 THEN EXIT SUB
       numItems = numItems + n
       nDim = UBOUND(item)
       IF nDim < numItems THEN 'redim arrays if necessary
          nDim = numItems + 1000
          REDIM PRESERVE item(nDim)
          REDIM PRESERVE itemRef(nDim)
       END IF
       FOR i = 1 TO n
          s = sParent + sTemp(i)
          item(numItems - n + i) = s
          itemRef(numItems - n + i).parent = itemNo
          itemRef(numItems - n + i).subDirs = subDirsExist(s)
       NEXT i
    END SUB
    
    FUNCTION subDirsExist(sItem AS STRING) AS BYTE
       'return 1 if any children exist for this node
       'this function is needed to determine if node needs a [+] button
       '----------------------------------------------------------------
       'sItem  = name of node
       '================================================================
       LOCAL attr, k AS LONG
       LOCAL s, sParent AS STRING
       sParent = RTRIM$(sItem, "\") + "\"
       attr = %SUBDIR 'OR %READONLY OR %HIDDEN OR %SYSTEM
       s = DIR$(sParent, attr)
       IF LEN(s) THEN
          k = GETATTR(sParent + s) AND %SUBDIR
          DO WHILE LEN(s) AND k = 0
             s = DIR$: IF LEN(s) = 0 THEN EXIT DO
             k = GETATTR(sParent + s) AND %SUBDIR
          LOOP
       END IF
       FUNCTION = SGN(k)
    END FUNCTION
    '--------------------------------------------------------------------------------
    Last edited by Fred Buffington; 15 Dec 2008, 01:48 AM.
    Client Writeup for the CPA

    buffs.proboards2.com

    Links Page

  • #2
    Fred, I couldn't find the starter code for treeview you mentioned, so just working with your code, I think I have addressed your problem. Note that I replaced the 'done' element in the itemRef UDT with 'expand'. I couldn't see any real reason for 'done' now.

    The getNextDirs() routine should only be called when the node is collapsed, not when its expanded. And the path of the selected file should just be dirToSearch$, so I made it Global in scope, not Local.

    Hope this addresses your concerns.

    Code:
    #COMPILE EXE
    #DIM ALL
    #INCLUDE "win32api.inc"
    #INCLUDE "Commctrl.inc"
    '#INCLUDE "debug.inc"
    
    'Include file statements
    '========================================================================
    %ID_TREE = 100
    
    TYPE itemRef_UDT
       hItem   AS LONG      'handle of item
       PARENT  AS LONG      'item number of parent
       subDirs AS BYTE      '1 = subdirectories exist, otherwise 0 
       expand  AS BYTE      '1 for expanded, 0 for collapsed
    END TYPE
    
    GLOBAL hTree AS LONG
    GLOBAL numItems AS LONG
    GLOBAL maxDim AS LONG
    GLOBAL ITEM() AS STRING
    GLOBAL itemRef() AS itemRef_UDT
    GLOBAL UserSelected$
    GLOBAL dirToSearch$
    
    DECLARE FUNCTION TVInsertItem(LONG, LONG, STRING, BYTE) AS LONG
    DECLARE FUNCTION getItemNo(LONG) AS LONG
    DECLARE FUNCTION subDirsExist(STRING) AS BYTE
    DECLARE SUB getNextDirs(LONG)
    '========================================================================
    
    FUNCTION PBMAIN()
       LOCAL hMain AS LONG
       LOCAL dummy$()
       DIM dummy$(0)
       DIALOG NEW PIXELS, 0, "my TreeView", , , 500, 400,%WS_SYSMENU OR %WS_MINIMIZEBOX TO hMain
        'add treeview common control
       CONTROL ADD "SysTreeView32", hMain, %ID_TREE, "", 0, 0, 300, 300, _
                  %WS_CHILD OR %WS_VISIBLE OR %TVS_HASBUTTONS OR %TVS_HASLINES OR _
                  %TVS_LINESATROOT OR %TVS_SHOWSELALWAYS, %WS_EX_CLIENTEDGE
       CONTROL ADD BUTTON, hMain,201,"&Close",100,320,80,30
       CONTROL ADD BUTTON, hMain,202,"&OK",100,360,80,30
       CONTROL ADD LISTBOX, hMain,301,dummy$(),310,0,170,300,%WS_VSCROLL 'OR %ES_MULTILINE
       CONTROL ADD TEXTBOX, hMain,302,"",310,320,120,22',%ES_MULTILINE OR %WS_VSCROLL
       DIALOG SHOW MODAL hMain CALL TreeViewProc
    MSGBOX "File is "+dirToSearch$+"\"+userSelected$
       END FUNCTION
    
    CALLBACK FUNCTION TreeViewProc()
       LOCAL i, m, n, hTreeItem, hItem, hParent, hChild AS LONG
       LOCAL s AS STRING, szTxt AS ASCIIZ*64
       LOCAL lpNmh AS NMHDR PTR
       LOCAL lpTV AS NM_TREEVIEW PTR
       LOCAL treeItem AS TV_ITEM
       LOCAL TVdi AS TV_DISPINFO
       LOCAL txt$,lResult$
       'LOCAL dirtosearch$
       LOCAL dummy$()
       LOCAL PATH$
    
       DIM dummy$(0)
       SELECT CASE CBMSG
          CASE %WM_INITDIALOG
             maxDim = 2000
             DIM ITEM(maxDim)
             DIM itemRef(maxDim)
    
             'find the first level children of C:\
             ITEM(0) = "C:": getNextDirs 0
    
             'display the folders and files
             CONTROL HANDLE CBHNDL, %ID_TREE TO hTree
             itemRef(0).hItem = TVInsertItem(hTree, 0, "C:\", 1)
             FOR i = 1 TO numItems
                s = PARSE$(ITEM(i), "\", -1)
                hParent = itemRef(itemRef(i).PARENT).hItem
                hChild = TVInsertItem(hTree, hParent, s, itemRef(i).subDirs)
                itemRef(i).hItem = hChild
             NEXT i
    
             'open with first level folders... the children of C:\
             SendMessage hTree, %TVM_EXPAND, %TVE_EXPAND, itemRef(0).hItem
          CASE %WM_PAINT
          CASE %WM_COMMAND
             SELECT CASE CBCTL
               CASE 201 'close button
                 UserSelected$=""
                 DIALOG END CBHNDL,0
               CASE 202 'ok
                 DIALOG END CBHNDL,1
               CASE 301 'select a file from textbox
                 LISTBOX GET TEXT CBHNDL,301 TO UserSelected$
                 CONTROL SET TEXT CBHNDL,302,UserSelected$
               CASE 302 'file name text box
                 CONTROL GET TEXT CBHNDL,302 TO UserSelected$
             END SELECT
          CASE %WM_NOTIFY
             lpNmh = CBLPARAM
             SELECT CASE @lpNmh.CODE
                CASE %TVN_ITEMEXPANDING
                   'expand or collapse the treeview node hTreeItem
                   lpTV = CBLPARAM
                   hTreeItem = @lpTV.ItemNew.hItem
                   m = numItems: n = getItemNo(hTreeItem)
                   IF itemRef(n).expand = 0 THEN getNextDirs n
                   
                   FOR i = m + 1 TO numItems
                      s = PARSE$(ITEM(i), "\", -1)
                      hParent = itemRef(itemRef(i).PARENT).hItem
                      hChild = TVInsertItem(hTree, hParent, s, itemRef(i).subDirs)
                      itemRef(i).hItem = hChild
                   NEXT i
    
                   txt$=""
                   LISTBOX RESET CBHNDL,301 
                   dirtosearch$=ITEM(n)
                   lResult$=DIR$(dirtosearch$+"\*.*",0)
                   WHILE lresult$<>""
                     txt$ =LResult$ 'txt$+lResult$+$CRLF
                     LISTBOX ADD CBHNDL,301,txt$
                     lResult$=DIR$
                   WEND
                CASE %TVN_SELCHANGING 'LAST 'FIRST 'KEYDOWN
                   lpTV = CBLPARAM
                   hTreeItem = @lpTV.ItemNew.hItem
                   m = numItems: n = getItemNo(hTreeItem)
                   txt$=""
                   LISTBOX RESET CBHNDL,301
                   dirtosearch$=ITEM(n)
                   lResult$=DIR$(dirtosearch$+"\*.*",0)
                   WHILE lresult$<>""
                     txt$ =lrEsult$ 'txt$+lResult$+$CRLF
                     LISTBOX ADD CBHNDL,301,txt$
                     'count&=count&+1
                     lResult$=DIR$
                   WEND
                  
             END SELECT
             
          CASE %WM_SYSCOMMAND
          CASE %WM_DESTROY
       END SELECT
    END FUNCTION
    
    FUNCTION getItemNo(hTreeItem AS LONG) AS LONG
       'get itemNo corresponding to item's handle
       'hTreeItem  = item handle
       'numItems   = number of items defined (nodes of treeview)
       '===================================================================
       LOCAL i, n AS LONG
       FOR i = 1 TO numItems
          IF itemRef(i).hItem = hTreeItem THEN n = i: EXIT FOR
       NEXT i
       FUNCTION = n
    END FUNCTION
    
    FUNCTION TVInsertItem(hTree AS LONG, hParent AS LONG, sTxt AS STRING, b AS BYTE) AS LONG
       'insert item into treeview control
       '--------------------------------------------------------------------------------
       'hTree   = handle of treeview control
       'hParent = handle of parent to item
       'sTxt    = string label identifying item
       'b       = set if children exist for this item
       '================================================================================
       LOCAL tv_insert AS TV_INSERTSTRUCT
       tv_insert.hParent              = hParent
       tv_insert.ITEM.ITEM.mask       = %TVIF_TEXT OR %TVIF_CHILDREN
       tv_insert.ITEM.ITEM.pszText    = STRPTR(sTxt)
       tv_insert.ITEM.ITEM.cchTextMax = LEN(sTxt)
       IF b THEN tv_insert.ITEM.ITEM.cChildren = 1
       FUNCTION = TreeView_InsertItem(hTree, tv_insert)
    END FUNCTION
    
    SUB getNextDirs(itemNo AS LONG)
       'find next first level children of itemNo
       'update global item(), itemRef(), and numItems
       '-------------------------------------------------------------------
       'item()    = array of folder names
       'itemRef() = array of folder info UDTs
       'numItems  = number of nodes identified so far
       '===================================================================
       LOCAL s, sParent AS STRING
       LOCAL attr, i, k, n, nDim AS LONG
       DIM sTemp(maxDim) AS STRING
       itemRef(itemNo).expand = 1
       sParent = RTRIM$(ITEM(itemNo), "\") + "\"
       attr = %SUBDIR 'OR %READONLY OR %HIDDEN OR %SYSTEM
       s = DIR$(sParent, attr)
       IF LEN(s) THEN
          k = GETATTR(sParent + s) AND %SUBDIR
          IF k THEN INCR n: sTemp(n) = s
          DO
             s = DIR$: k = 0: IF LEN(s) = 0 THEN EXIT DO
             k = GETATTR(sParent + s) AND %SUBDIR
             IF k THEN INCR n: sTemp(n) = s
          LOOP
       END IF
       'found n subdirectories of itemNo
       '... now update numItems, item(), and itemRef()
       IF n = 0 THEN EXIT SUB
       numItems = numItems + n
       nDim = UBOUND(ITEM)
       IF nDim < numItems THEN 'redim arrays if necessary
          nDim = numItems + 1000
          REDIM PRESERVE ITEM(nDim)
          REDIM PRESERVE itemRef(nDim)
       END IF
       FOR i = 1 TO n
          s = sParent + sTemp(i)
          ITEM(numItems - n + i) = s
          itemRef(numItems - n + i).PARENT = itemNo
          itemRef(numItems - n + i).subDirs = subDirsExist(s)
       NEXT i
    END SUB
    
    FUNCTION subDirsExist(sItem AS STRING) AS BYTE
       'return 1 if any children exist for this node
       'this function is needed to determine if node needs a [+] button
       '----------------------------------------------------------------
       'sItem  = name of node
       '================================================================
       LOCAL attr, k AS LONG
       LOCAL s, sParent AS STRING
       sParent = RTRIM$(sItem, "\") + "\"
       attr = %SUBDIR 'OR %READONLY OR %HIDDEN OR %SYSTEM
       s = DIR$(sParent, attr)
       IF LEN(s) THEN
          k = GETATTR(sParent + s) AND %SUBDIR
          DO WHILE LEN(s) AND k = 0
             s = DIR$: IF LEN(s) = 0 THEN EXIT DO
             k = GETATTR(sParent + s) AND %SUBDIR
          LOOP
       END IF
       FUNCTION = SGN(k)
    END FUNCTION

    Comment


    • #3
      Thanks Charles, works perfectly.
      BTW, here is where the starter code is:
      Last edited by Fred Buffington; 15 Dec 2008, 03:53 PM.
      Client Writeup for the CPA

      buffs.proboards2.com

      Links Page

      Comment


      • #4
        I couldn't help trying a recursive method to do the same thing (in a crude way) and in doing it I discovered that DIR$ doesn't work in a recursive SUB - which is pretty much what the F1 help says, actually. So instead, Windows FindFirstFile/FindNextFile was used.

        Also I did not check for other examples on the forum, there could be dozens...

        Anyway, the result is pleasingly compact.

        If you want to try it, you will have to edit in the start path in the WM_INITDIALOG handler - I did say it was crude!




        Code:
        ' TREEVIEW DIR LIST
        ' by Chris Holbrook 15-DEC-2008
        ' Application to investigate using a treeview to list directories
        ' it doesn't use DIR$ because this does not work in a recursive SUB.
        ' to change the start directory seethe WM_INITDIALOG handler!
        '
        #compile exe
        #dim all
        #include  "commctrl.inc"
        
        %IDC_DBB_TREEVIEW      = 1320
        %IDC_TEXTBOX1          = 1323
        '------------------------------------------------------------------------------
        function SampleTreeViewInsertItem(byval hTree as dword, byval hParent as dword, sItem as string, licon as long) as long
            local tTVItem   as TV_ITEM
            local tTVInsert as TV_INSERTSTRUCT
        
            if hParent then
                tTVItem.mask      = %TVIF_CHILDREN or %TVIF_HANDLE
                tTVItem.hItem     = hParent
                tTVItem.cchildren = 1
                TreeView_SetItem hTree, tTVItem
            end if
        
            tTVInsert.hParent              = hParent
            tTVInsert.Item.Item.mask       = %TVIF_TEXT or %TVIF_IMAGE or %TVIF_SELECTEDIMAGE
            tTVInsert.Item.Item.pszText    = strptr(sItem)
            tTVInsert.Item.Item.cchTextMax = len(sItem)
            ttvinsert.Item.Item.iImage         = licon
            ttvinsert.Item.Item.iSelectedImage = licon
        
            function = TreeView_InsertItem(hTree, tTVInsert)
        end function
        '------------------------------------------------------------------------------
        sub SampleTreeView(byval hCtl as dword, byval hParent as dword, byval Startdir as asciz ptr)
            local hdir as dword
            local item as long
            local fn        as asciiz * %MAX_PATH
            local FindData  as WIN32_FIND_DATA
            local s as string
        
            FindData.dwFileAttributes = %FILE_ATTRIBUTE_DIRECTORY  or %FILE_ATTRIBUTE_NORMAL
            fn = @startdir + "\*.*"
            hDir = FindFirstFile(fn, FindData)
            if hDir = %INVALID_HANDLE_VALUE then
                ? "Cannot read directory!"
                exit sub
            end if
            do
                fn = FindData.cFileName
                if trim$(fn) = "." or trim$(fn) = ".." then iterate
                s = fn
                item = SampleTreeViewInsertItem(hCtl, hParent, s, 0)
                s = @startdir + "\" + fn
                if (getattr (@startdir + "\" + fn) and %SUBDIR) = %SUBDIR then
                    SampleTreeView(hCtl, item, byval strptr(s))
                end if
                fn = dir$
            loop while FindNextFile(hDir, FindData)
        
        end sub
        '------------------------------------------------------------------------------
        callback function CBProc()
            static hparent, hTVWnd as dword
            local sz            as asciiz*%MAX_PATH
            local lpNmh         as NMHDR ptr
            local treeItem      as TV_ITEM
            local TVht          as TV_HITTESTINFO
            local DWPOS         as dword
        
            select case as long cbmsg
                case %wm_initdialog
                    ' Initialization handler
                    hTVWnd = getdlgitem(cbhndl, %IDC_DBB_TREEVIEW)
                    sz = "c:\Program Files"
                    SampleTreeView(hTVWnd, %NULL, byval varptr(sz))
                 '
                 case %wm_notify
                     lpNmh = cblparam
                     select case @lpNmh.Code
        
                        case %nm_click
                          if @lpNmh.idFrom = %IDC_DBB_TREEVIEW then
                            ' This will ensure that when the user either clicks the checkbox or the
                            ' buttons (+/-) the item/node will be selected. You get a strange flashing
                            ' experience if you do not do this, comment out this code and then use a
                            ' checkbox or the buttons and see what occurs. (found this code on MSDN)
        
                            dwPos = GetMessagePos          ' Get the mouse position
                            TVht.pt.x = lo(word, dwPos)    ' Store the mouse x pos in the hittest udt
                            tvht.pt.y = hi(word, dwPos)    ' Store the mouse y pos in the hittest udt
                            MapWindowPoints(%HWND_DESKTOP, @LPnMH.hwndFrom, TVht.pt, 1)
                            ' Get the TreeView item/node returned in the hittest udt member of hItem
                            SendMessage(hTVWnd, %TVM_HITTEST, 0, byval varptr(TVht))
                            ' get item
                            TreeItem.mask       = %TVIF_STATE or %TVIF_HANDLE
                            TreeItem.hItem      = TVHT.Hitem
                            sendmessage hTVWnd, %TVM_GETITEM, 0, varptr(TreeItem)
                            ' Select the TreeView item/node that was clicked on (returned above)
                            SendMessage(hTVWnd, %TVM_SELECTITEM, %TVGN_CARET, TVht.hItem)
                          end if
                     end select
        
                case %wm_lbuttondown
                    SendMessage cbhndl, %wm_nclbuttondown, %HTCAPTION, byval %NULL  ' force drag
        
                case %wm_command
                select case cbctl  ' <- look at control's id
                end select
            end select
        end function
        '------------------------------------------------------------------------------
        function DBBrowser(byval hParent as dword) as long
            local lRslt as long
            local hDlg  as dword
            local s as string
        
            dialog new hParent, "DIR TV  Chris Holbrook Dec 2008", 10, 100, 170, 205, %ws_popup  or _
                %ws_clipsiblings or %ws_visible or %ws_sysmenu or %ws_dlgframe or %ws_border or _
                %ds_modalframe or %ds_3dlook or %ds_nofailcreate or _
                %ds_setfont, %ws_ex_controlparent or _
                %ws_ex_topmost or %ws_ex_left or %ws_ex_ltrreading to hdlg
            control add "SysTreeView32", hDlg,%IDC_DBB_TREEVIEW, "SysTreeView32_1", _
                0, 0, 170, 200, %ws_child or %ws_visible or %ws_tabstop or _
                %tvs_hasbuttons or %tvs_haslines or %tvs_checkboxes or %tvs_linesatroot _
                or %tvs_showselalways, %ws_ex_clientedge or _
                %ws_ex_left or %ws_ex_ltrreading or %ws_ex_rightscrollbar
            dialog show modal hDlg, call CBProc to lRslt
            function = lRslt
        end function
        '---------------------------------------------------------------------------------
        function pbmain () as long
            InitCommonControls
            DBBrowser(0)
        end function

        Comment


        • #5
          Chris, your version compiled and 'ran' but wouldn't show up on my ME computer.
          Well that's not altogether true. I got it to com up once by
          running it twice and removing it once with the task manager. All the other
          times I had to stop it by using the task manager. Compiled with pbwin 8+.

          I didnt try it on vista.
          Client Writeup for the CPA

          buffs.proboards2.com

          Links Page

          Comment


          • #6
            Fred, I believe that the recursive algorithm just takes so long to expand all of the nodes and determine all of the files for each node before it is displayed. I too was fooled by the long delay. I also had trouble running the code with sz = "c:" (it kept getting an INVALID_HANDLE_VALUE, couldn't figure out why)

            The long time required before display was what led me to develop my code to only expand nodes and determine files as needed by the user.

            Comment


            • #7
              Yes you are both right. It may be compact, but is awful.

              So I have improved it by expanding the branches on demand rather than putting everything into the TV at first. It goes a lot quicker.

              Win32 Programmers Reference version of things has a reference to I_CHILDRENCALLBACK (see TV_ITEM.cChildren). MSDN references have changed, I don't know whether this approach is still viable, and don't have time to look just now.

              In the code below (PB 8) a flag in the lParam member of the TV_ITEM is used to indicate whether the subdirectory has been expanded or is a stub, with the cChildren count set to one to force the [+] to appear against stubs. When the branch is expanded this flag is cleared.

              The essential diference between this approach and Charles's is that here the TV is used to contain all the information needed to manage it.

              Code:
              ' TREEVIEW DIR LIST
              ' by Chris Holbrook 15-DEC-2008
              ' Application to investigate using a treeview to list directories
              ' it doesn't use DIR$ because this does not work in a recursive SUB.
              ' to change the start directory see the WM_INITDIALOG handler!
              '
              ' changes
              ' 17-Dec-2008 performance improved by expanding branches on demand
              '
              #compile exe
              #dim all
              #include "commctrl.inc"
              
              %IDC_DBB_TREEVIEW      = 1320
              %IDC_TEXTBOX1          = 1323
              ' list of values used in lParam member of TC_ITEM to indicate
              ' whether item is a stub for an unexpanded directory or not
              %DTV_STUB    = 1
              %DTV_NOTSTUB = 0
              
              global ghTreeImgList as dword
              global gRootofSearch as string
              '------------------------------------------------------------------------------
              function DirTreeViewInsertItem(byval hTree as dword, _
                                             byval hParent as dword, _
                                             sItem as string, _
                                             licon as long, _
                                             expandable as long) as long
                  local tTVItem   as TV_ITEM
                  local tTVInsert as TV_INSERTSTRUCT
              
                  if hParent then
                      tTVItem.mask      = %TVIF_CHILDREN or %TVIF_HANDLE
                      tTVItem.hItem     = hParent
                      tTVItem.cchildren = 1
                      TreeView_SetItem hTree, tTVItem
                  end if
              
                  tTVInsert.hParent              = hParent
                  tTVInsert.Item.Item.mask       = %TVIF_TEXT or %TVIF_IMAGE or %TVIF_SELECTEDIMAGE or %TVIF_PARAM or %TVIF_CHILDREN
                  tTVInsert.Item.Item.pszText    = strptr(sItem)
                  tTVInsert.Item.Item.cchTextMax = len(sItem)
                  ttvinsert.Item.Item.iImage         = licon
                  ttvinsert.Item.Item.iSelectedImage = licon
                  ttvinsert.Item.Item.lParam         = expandable
                  if expandable then ttvinsert.Item.Item.cChildren = 1
              
                  function = TreeView_InsertItem(hTree, tTVInsert)
              end function
              '------------------------------------------------------------------------------
              sub DirTreeView(byval hCtl as dword, byval hParent as dword, byval Startdir as asciz ptr)
                  local hdir as dword
                  local item, subitem as long
                  local fn        as asciiz * %MAX_PATH
                  local FindData  as WIN32_FIND_DATA
                  local s as string
              
                  FindData.dwFileAttributes = %FILE_ATTRIBUTE_DIRECTORY  or %FILE_ATTRIBUTE_NORMAL
                  fn = @startdir + "\*.*"
                  hDir = FindFirstFile(fn, FindData)
                  if hDir = %INVALID_HANDLE_VALUE then
                      ? "Cannot read directory!"
                      exit sub
                  end if
                  do
                      fn = FindData.cFileName
                      if trim$(fn) = "." or trim$(fn) = ".." then iterate
                      s = fn
                      if (getattr (@startdir + "\" + fn) and %SUBDIR) = %SUBDIR then
                          item = DirTreeViewInsertItem(hCtl, hParent, s, 2, %DTV_STUB)
                      else
                          item = DirTreeViewInsertItem(hCtl, hParent, s, 2, %DTV_NOTSTUB)
                      end if
                      fn = dir$
                  loop while FindNextFile(hDir, FindData)
              
              end sub
              '------------------------------------------------------------------------------
              ' this sub ascends the tree from the current item, prepending the path varaible
              ' with with each preceding parent directory'e name.
              ' Assumption: that the hItem part of the structure pointed to by 2nd param
              ' is valid.
              '
              function GetDirPath ( hTVWnd as dword, byval pTV as TV_ITEM ptr) as string
                  local TreeItem as TV_ITEM
                  local i as long
                  static spath as string
                  local sz as asciz * %MAX_PATH
              
                  spath = ""
                  TreeItem = @pTV
                  TreeItem.mask = %TVIF_HANDLE or %TVIF_TEXT
                  TreeItem.psztext = varptr(sz)
                  Treeitem.cchTextMax = %MAX_PATH
                  SendMessage(hTVWnd, %TVM_GETITEM, 0, byval varptr(TreeItem))
                  spath = sz
                  TreeItem.hItem = TreeView_GetParent ( hTVWnd, TreeItem.hitem)
                  while TreeItem.hItem <> %NULL
                      TreeItem.mask = %TVIF_STATE or %TVIF_HANDLE or %TVIF_TEXT
                      SendMessage(hTVWnd, %TVM_GETITEM, 0, byval varptr(TreeItem))
                      spath = [email protected] + "\" + spath
                      TreeItem.hItem = TreeView_GetParent ( hTVWnd , TreeItem.hitem)
                  wend
                  function = gRootofSearch + "\" + spath
              end function
              
              '------------------------------------------------------------------------------
              callback function CBProc()
                  static hparent, hTVWnd as dword
                  local sz            as asciiz*%MAX_PATH
                  local lpNmh         as NMHDR ptr
                  local treeItem      as TV_ITEM
                  local TVht          as TV_HITTESTINFO
                  local DWPOS         as dword
                  local htreeitem     as dword
                  local lr as long
                  local s as string
                  
                  select case as long cbmsg
                      case %wm_initdialog
                          ' Initialization handler
                          hTVWnd = getdlgitem(cbhndl, %IDC_DBB_TREEVIEW)
                              ' Add image list to TreeView
              
                          gRootofSearch = "c:\Program Files"
                          DirTreeView(hTVWnd, %NULL, byval strptr(GrootofSearch))
                       '
                       case %wm_notify
                           lpNmh = cblparam
                           select case @lpNmh.Code
                               
                              case %TVN_ITEMEXPANDING
                                dwPos = GetMessagePos          ' Get the mouse position
                                TVht.pt.x = lo(word, dwPos)    ' Store the mouse x pos in the hittest udt
                                tvht.pt.y = hi(word, dwPos)    ' Store the mouse y pos in the hittest udt
                                MapWindowPoints(%HWND_DESKTOP, @LPnMH.hwndFrom, TVht.pt, 1)
                                ' Get the TreeView item/node returned in the hittest udt member of hItem
                                SendMessage(hTVWnd, %TVM_HITTEST, 0, byval varptr(TVht))
                                ' get item
                                TreeItem.mask       = %TVIF_STATE or %TVIF_HANDLE or %TVIF_PARAM
                                TreeItem.hItem      = TVHT.Hitem
                                sendmessage hTVWnd, %TVM_GETITEM, 0, varptr(TreeItem)
                                ' construct the path to the selected parent dir by going "up tree"
                                if TreeItem.lParam = %DTV_STUB then
                                    ' clear the indicator in lparam
                                    TreeItem.lParam = %DTV_NOTSTUB
                                    sendmessage hTVWnd, %TVM_SETITEM, 0, varptr(TreeItem)
              
                                    s = getdirpath ( hTVWnd, byval varptr(TreeItem))
                                    ' expand the directory whose path has been discovered
                                    DirTreeView(hTVWnd, TVHT.hItem, byval strptr(s))
                                end if
                                
                              case %nm_click
                                if @lpNmh.idFrom <> %IDC_DBB_TREEVIEW then exit select
                                ' This will ensure that when the user either clicks the checkbox or the
                                ' buttons (+/-) the item/node will be selected. You get a strange flashing
                                ' experience if you do not do this, comment out this code and then use a
                                ' checkbox or the buttons and see what occurs. (found this code on MSDN)
              
                                dwPos = GetMessagePos          ' Get the mouse position
                                TVht.pt.x = lo(word, dwPos)    ' Store the mouse x pos in the hittest udt
                                tvht.pt.y = hi(word, dwPos)    ' Store the mouse y pos in the hittest udt
                                MapWindowPoints(%HWND_DESKTOP, @LPnMH.hwndFrom, TVht.pt, 1)
                                ' Get the TreeView item/node returned in the hittest udt member of hItem
                                SendMessage(hTVWnd, %TVM_HITTEST, 0, byval varptr(TVht))
                                ' get item
                                TreeItem.mask       = %TVIF_STATE or %TVIF_HANDLE
                                TreeItem.hItem      = TVHT.Hitem
                                sendmessage hTVWnd, %TVM_GETITEM, 0, varptr(TreeItem)
                                ' Select the TreeView item/node that was clicked on (returned above)
                                SendMessage(hTVWnd, %TVM_SELECTITEM, %TVGN_CARET, TVht.hItem)
                           end select
              
                      case %wm_lbuttondown
                          SendMessage cbhndl, %wm_nclbuttondown, %HTCAPTION, byval %NULL  ' force drag
              
                      case %wm_command
                      select case cbctl  ' <- look at control's id
                      end select
                  end select
              end function
              '------------------------------------------------------------------------------
              function dlg(byval hParent as dword) as long
                  local lRslt as long
                  local hDlg  as dword
                  local s as string
              
                  dialog new hParent, "DIR TV  Chris Holbrook Dec 2008", 10, 100, 170, 205, %ws_popup  or _
                      %ws_clipsiblings or %ws_visible or %ws_sysmenu or %ws_dlgframe or %ws_border or _
                      %ds_modalframe or %ds_3dlook or %ds_nofailcreate or _
                      %ds_setfont, %ws_ex_controlparent or _
                      %ws_ex_topmost or %ws_ex_left or %ws_ex_ltrreading to hdlg
                  control add "SysTreeView32", hDlg,%IDC_DBB_TREEVIEW, "SysTreeView32_1", _
                      0, 0, 170, 200, %ws_child or %ws_visible or %ws_tabstop or _
                      %tvs_hasbuttons or %tvs_haslines  or %tvs_linesatroot _ 'or %tvs_checkboxes
                      or %tvs_showselalways, %ws_ex_clientedge or _
                      %ws_ex_left or %ws_ex_ltrreading or %ws_ex_rightscrollbar
                  dialog show modal hDlg, call CBProc to lRslt
                  function = lRslt
              end function
              '---------------------------------------------------------------------------------
              function pbmain () as long
                  InitCommonControls
                  dlg(0)
              end function

              Comment


              • #8
                ..I believe that the recursive algorithm just takes so long to expand all of the nodes
                Code:
                ' ============================================================================
                ' Fully Expand the treeview node at hNode
                ' SAMPLE CALL using the macros available in COMMCTRL.INC (Windows standard macros)
                ' iexpand:  True = expand, false  Collapse
                ' TreeView_ExpandNode GetDlgItem(hWnd, %ID_TV),_
                '     TreeView_GetSelection(GetDlgItem(hWnd,%ID_TV)), %TRUE, %FALSE
                '        true, false = expand, no siblings of selected
                ' =============================================================================
                FUNCTION TreeView_ExpandNode (hTV AS LONG, hNode AS LONG, BYVAL iExpand AS LONG, BYVAL doSiblings AS LONG) AS LONG
                
                    LOCAL hItem AS LONG, hChild AS LONG, Expand AS LONG, doSibs AS LONG
                    ' get stuff into LOCAL vars to make sure each recursion gets own copy
                    doSibs = DoSiblings
                    Expand = IIF(iExpand, %TVE_EXPAND, %TVE_COLLAPSE)
                    hItem = hNode
                    DO WHILE hItem
                      SendMessage hTV, %TVM_EXPAND, Expand, hItem
                     ' if any children, call this function recursively.
                     ' The getChild macro returns the first child of hItem in hTv
                      hChild = TreeView_GetChild(hTV,hItem)
                      IF hChild THEN
                         TreeView_ExpandNode hTv, hChild, Expand, %TRUE    ' when doing children, we always do siblings
                      END IF
                      ' and make hitem the next sibling at this level
                      IF DoSibs THEN
                          hItem = TreeView_GetNextSibling (hTv, hItem)
                      ELSE
                          hItem = %NULL
                      END IF
                    LOOP
                
                END FUNCTION
                HINT:
                Code:
                MACRO Redraw_Off (hCtrl)
                  SendMessage hCtrl, %WM_SETREDRAW, %FALSE, 0&
                END MACRO
                MACRO Redraw_On (hCtrl)
                  SendMessage hCtrl, %WM_SETREDRAW, %TRUE, 0&
                END MACRO
                
                ... 
                     REDRAW_OFF  (hTv)    ' no sense redrawing tree 'till we're done, right? 
                     Call expander function
                     REDRAW_ON  (hTV) 
                     UpdateWindow   hTV
                Last edited by Michael Mattias; 17 Dec 2008, 08:06 AM.
                Michael Mattias
                Tal Systems (retired)
                Port Washington WI USA
                [email protected]
                http://www.talsystems.com

                Comment


                • #9
                  Originally posted by Chris Holbrook View Post
                  Win32 Programmers Reference version of things has a reference to I_CHILDRENCALLBACK (see TV_ITEM.cChildren). MSDN references have changed, I don't know whether this approach is still viable, and don't have time to look just now.
                  Had a chance to check this today and there was just one reference in the forums, a nice little application by Kev Peel which uses this approach. No surprises there then! The sad part is Kev posted it in 2003, but what's wrong with re-inventing the wheel, eh?

                  Comment


                  • #10
                    I guess I was just impatient. And this computer running ME does take a while on some things. It's like an 600/800 mhz.machine

                    Yes, Chris, that is what I did with my ddt version that I posted some time ago.
                    That is, only get the top folders and find subfolders (and files) on demand.
                    Which is basically what the mod to Charles Starter code does (at least as far as files are concerned).
                    Last edited by Fred Buffington; 17 Dec 2008, 03:55 PM.
                    Client Writeup for the CPA

                    buffs.proboards2.com

                    Links Page

                    Comment


                    • #11
                      Originally posted by Fred Buffington View Post
                      I guess I was just impatient.
                      You couldn't be as impatient as me, surely!

                      I was half thinking about writing something like this for myself (as a basis for a photo browser) when I saw this thread. I am very tempted to build up on Kev Peel's application, detuned to look only at the local hard disk. His code does what mine would do if I spent a week on it.

                      Comment

                      Working...
                      X