Link to home
Start Free TrialLog in
Avatar of Paurths
Paurths

asked on

recursive - can this be done?

hi,

i have table that holds all the folders for a drive.
The table is made up like this:

FolderID  (autonumber - Pr.Key)
Foldername (name of the folder...)
FolderLevel (C: = 0, Winnt = 1, System32 = 2, etc)
FolderParentID (FolderID of the parentfolder, if Winnt
                has folderid 15, then system32 will
                have 15 as folderparentID)


this way i am able to use this table several times in one query to show a folder-structure.
Another table holds the nt-security-settings for each folder (with the local and global groups)

but, u guessed, this is not exactly a nice way of working

so i was thinking of loading this into a treeview,
and i assume i will need some sort of recursive function(s) for it to accomplish.

is this correct?
if so, has anyone have any experience with this  kind of thing?
again, if so, any directions...?

cheers
Ricky
Avatar of aikimark
aikimark
Flag of United States of America image

Not really.  You aren't bound to use a recursive function. I'm not entirely sure I understand your problem or why you're changing something that already works.

Are you looking for alternatives?
Is your problem mostly a processing problem?
Performance problem?
Directory name/parentage data gathering problem?

I assume you need to support cascading security, group security, as well as user-specific security.  If I'm wrong, please let me know.

What are the details of this application?
In addition:

What do you want to do with hidden and system directories?

What does the user need to do?  I ask this question because the Treeview control can slow your application needlessly if the user doesn't need to access this information.
Avatar of Paurths
Paurths

ASKER

hi akimark,

actually, i am still developing this program,
it has all (i hope) the issues about network.
routers, outlets, patchpanel, computers, printers, printservers (jet-direct and axis), desks, users, devisions, locations, local groups, global groups, etc...

this is only a small part of the entire program.
What i would like to do is:
if i could fill a treeview with all the folders, click on a node (e.g. a folder on a shared folder) immediatly know what groups, users, have what permissions.
I know i can retrieve this information in my form where the folders are shown/added.
I was just wondering if it can be done. (i would probably be the only person on this planet using this program, but i figured it would be good excercice...)

cheers
Ricky
The quickest/easiest development effort will use a Directory (intrinsic) control.  When the user selects a directory, you gather the permissions and display them.

If you gather all the permissions, you can either loop through them, looking for candidates with the LIKE operator, or place them in a table and use SQL with a WHERE LIKE search criteria.

You will possibly have several rules that will cascade (apply) to the selected directory.
Dim ParentFolder as Scripting.Folder
Dim oFolder as Scripting.Folder

Private Sub Form_Load()
    Dim oFSO as Scripting.FileSystemObject
    Set oFolder = oFSO.GetFolder("your\path")
    Call DoTree(oFolder)
End Sub

Private Function DoTree(oFolder as Scripting.Folder)
    For Each Folder In oFolder.Folders
        'Code to Process the folder
        Call DoTree(oFolder)
    Next
End Function
Here is a snippet I just did in an Access Module (referencing mscomctl.ocx) for loading an employee hierachy from the CEO down..

This assumes you have an MSAccess table called "tblEmp"
Fields:
EmpID (Long)
EmpName (Text)
ParentEmpID (Long)

Whack the following functions in a vb module or form class and call the function:

BuldTree TreeView1

in Form_Load event

Change the following code to loop your FileSystemObjects instead of the tblEmp table records... good luck!

Option Explicit

Public Sub BuildTree(oTreeView As TreeView)
   
    Dim sql         As String
    Dim rs          As Recordset
    Dim NodX        As Node
   
On Error GoTo ET


'    With oTreeView
'        .Font = 1 ' .Font = "Tahoma"
'        .Font.Size = "12"
'        .Font.Bold = True
'        '.Indentation = "500"
'        .LabelEdit = False
'        '.LineStyle = 1
'        '.Style = 7
'        '.FullRowSelect = True
'    End With

'The following comments is a sample of adding nodes to the tree
       
'    Set nodX = oTreeView.Nodes.Add(, , "R", "Root")
'    Set nodX = oTreeView.Nodes.Add("R", tvwChild, "C1", "Child 1")
'    Set nodX = oTreeView.Nodes.Add("R", tvwChild, "C2", "Child 2")
'    Set nodX = oTreeView.Nodes.Add("R", tvwChild, "C3", "Child 3")
'    Set nodX = oTreeView.Nodes.Add("R", tvwChild, "C4", "Child 4")
'    Set nodX = oTreeView.Nodes.Add("C4", tvwChild, "C5", "Child 5")
'    Set nodX = oTreeView.Nodes.Add("C5", tvwChild, "C6", "Child 6")
'    nodX.EnsureVisible
   
'Add Parent Root Node ie. CEO

    'assume CEO's EmpID=1
    Set NodX = oTreeView.Nodes.Add(, , "Key=1", DLookup("EmpName", "tblEmp", "EmpID = 1"))

    AddChildNodes oTreeView, 1

    NodX.Bold = True
    NodX.ForeColor = 255
   
    Exit Sub

ET:
    MsgBox Err.Number & " - " & Err.Description, vbCritical
End Sub

Private Sub AddChildNodes(oTreeView As TreeView, EmpID As Integer)
   
    Dim rs      As Recordset
    Dim sql     As String
    Dim NodX    As Node
   
    sql = "SELECT EmpID, EmpName, ParentEmpID"
    sql = sql & " FROM tblEmp"
    sql = sql & " WHERE ParentEmpID = " & EmpID
    sql = sql & " ORDER BY EmpID;"
    Set rs = CurrentDb.OpenRecordset(sql, dbOpenForwardOnly)
   
    Do Until rs.EOF
        Set NodX = oTreeView.Nodes.Add("Key=" & EmpID, tvwChild, "Key=" & rs!EmpID, rs!EmpName)
        AddChildNodes oTreeView, rs!EmpID
        rs.MoveNext
    Loop
    rs.Close
    Set rs = Nothing

End Sub

Public Sub DelTree(oTreeView As TreeView)

    Do While oTreeView.Nodes.Count > 0
        oTreeView.Nodes.Remove (1)
    Loop
   
End Sub
ASKER CERTIFIED SOLUTION
Avatar of aikimark
aikimark
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I wrote a form to do pretty much what you are asking.  First off, yes, you can use recursion to go through all folders and load them into a treeview but a word of warning, it's very slow.  You might not realize it but there are maybe hundreds or thousands of folders under Program Files, WINNT or some of the major folders.  Even if you are just looking at directories, it can take a long time to load the tree.

I finally settled on loading one level at a time.  So, first load all of the top level folders.  Then, as you expand a folder just get it's direct child folders etc.

My example was also trying to track the folder sizes to help me locate disk-hogs.  Here is some of the code.  If you want the stuff that actually gets the directory size let me know.  With a little tinkering this should do what you want.

Open a projet with a Treeview, a ListView, a Drive File List, name them appropriatly and paste in this code.  You might have to comment out the calls to GetDirectorySize.

Private Sub Drive1_Change()
Dim oNode As Node
    tvwDirectory.Nodes.Clear
    Set Node = tvwDirectory.Nodes.Add(, , Left$(Drive1.Drive, 1) & ":", Left$(Drive1.Drive, 1) & ":", 1)
    GetDirContents Left$(Drive1.Drive, 1) & ":"
    tvwDirectory_NodeClick tvwDirectory.Nodes(1)
    tvwDirectory.Nodes(1).Expanded = True
End Sub

Private Sub Form_Load()
    FormLoading = True
    lvwDisplay.View = lvwReport
    lvwDisplay.ColumnHeaders.Add , , "Name", lvwDisplay.Width * 0.3, lvwColumnLeft
    lvwDisplay.ColumnHeaders.Add , , "Size", lvwDisplay.Width * 0.2, lvwColumnRight
    lvwDisplay.ColumnHeaders.Add , , "Relative", lvwDisplay.Width * 0.5, lvwColumnLeft
   
    Drive1.Drive = "C:"
    Drive1_Change
    FormLoading = False
       
End Sub

Private Sub GetDirContents(sPath As String)
Dim oNode As Node
Dim sTemp As String
Dim sMatch As String

    If tvwDirectory.Nodes(sPath).Children > 0 Then Exit Sub
   
    If Right$(sPath, 1) = "\" Then
        sTemp = sPath
    Else
        sTemp = sPath & "\"
    End If
   
    sMatch = Dir$(sTemp & "\*.", vbDirectory)
   
    Do While sMatch <> ""
        If sMatch <> "." And sMatch <> ".." Then
            If ((GetAttr(sTemp & sMatch) And vbDirectory) = vbDirectory) Then
                Set Node = tvwDirectory.Nodes.Add(sPath, tvwChild, sTemp & sMatch, sMatch, 2)
            End If
        End If
        sMatch = Dir$()
    Loop
   

End Sub

Private Sub tvwDirectory_Collapse(ByVal Node As MSComctlLib.Node)
    Node.Image = 2
End Sub

Private Sub tvwDirectory_Expand(ByVal Node As MSComctlLib.Node)
Dim oNode As Node
Dim vIndex As Variant
    Node.Image = 3
    GetDirContents Node.Key
    If Node.Children > 0 Then
        Set oNode = Node.Child
'        GetDirContents oNode.Key
        If Not oNode.LastSibling Is Nothing Then
            vIndex = oNode.LastSibling.Index
            Do While Not oNode Is Nothing
                GetDirContents oNode.Key
                If oNode.Index <> vIndex Then
                    Set oNode = oNode.Next
                Else
                    Set oNode = Nothing
                End If
            Loop
        End If
    End If

End Sub

Private Sub tvwDirectory_NodeClick(ByVal Node As MSComctlLib.Node)
Dim oNode As Node
Dim oListItem As ListItem
Dim vIndex As Variant
Dim dblDirSize As Double
Dim dblTemp As Double

    lvwDisplay.ListItems.Clear
   
    GetDirContents Node.Key
    Set oListItem = lvwDisplay.ListItems.Add(, , "Files", 4, 4)
 '   dblDirSize = GetDirectorySize(Node.Key)
    oListItem.SubItems(1) = Format((dblDirSize + 1) / 1000, "###,###,###,##0") & "KB"
    oListItem.SubItems(2) = "*****"
    If Node.Children > 0 Then
        Set oNode = Node.Child
'        GetDirContents oNode.Key
        If Not oNode.LastSibling Is Nothing Then
            vIndex = oNode.LastSibling.Index
            Do While Not oNode Is Nothing
                GetDirContents oNode.Key
                Set oListItem = lvwDisplay.ListItems.Add(, , oNode.Text, 2, 2)
                dblDirSize = GetDirectorySize(oNode.Key)
                oListItem.SubItems(1) = Format((dblDirSize + 1) / 1000, "###,###,###,##0") & "KB"
                oListItem.SubItems(2) = "*****"
                If oNode.Index <> vIndex Then
                    Set oNode = oNode.Next
                Else
                    Set oNode = Nothing
                End If
            Loop
        End If
    End If
    dblDirSize = 0
    For i = 1 To lvwDisplay.ListItems.Count
        Set oListItem = lvwDisplay.ListItems(i)
        dblDirSize = dblDirSize + CDbl(Left(oListItem.SubItems(1), Len(oListItem.SubItems(1)) - 2))
    Next i
    For i = 1 To lvwDisplay.ListItems.Count
        Set oListItem = lvwDisplay.ListItems(i)
        dblTemp = CDbl(Left(oListItem.SubItems(1), Len(oListItem.SubItems(1)) - 2))
        If dblTemp > 0 Then
            oListItem.SubItems(2) = String(((dblTemp / dblDirSize) * 100), "*")
        End If
    Next i
   
End Sub
Avatar of Paurths

ASKER

hi all,

actually, this one works for me:

code provided by ameba:

**************************************
To load a tree, I suggest Chris Eastwood's code, which first adds all nodes to root, and later reparents
them. .Tag property is used to cache ParentID.


Private Sub FillTree()
   Dim lCount As Long
   Dim rsSections As Recordset
   Dim sParent As String
   Dim sKey As String
   Dim sText As String
   Dim bBookMark As Boolean
   Dim nNode As Node
   On Error GoTo vbErrorHandler
'
' Populate our TreeView Control with the Data from our database
'
   Set rsSections = mDB.OpenRecordset("select * from codeitems order by parentid")
   
   Set tvCodeItems.ImageList = Nothing
   Set tvCodeItems.ImageList = ImageList1

   If rsSections.BOF And rsSections.EOF Then
       tvCodeItems.Nodes.Add , , "ROOT", "Code Library", "VIEWBOOKMARKS"
       BoldTreeNode tvCodeItems.Nodes("ROOT")
       Exit Sub
   End If
       
   TreeRedraw tvCodeItems.hwnd, False
   
   rsSections.MoveFirst
   Set tvCodeItems.ImageList = Nothing
   Set tvCodeItems.ImageList = ImageList1
'
' Populate the TreeView Nodes
'

   With tvCodeItems.Nodes
       .Clear
       .Add , , "ROOT", "Code Library", "VIEWBOOKMARKS"
'
' Make our Root Item BOLD
'
       BoldTreeNode tvCodeItems.Nodes("ROOT")
'
' Now add all nodes into TreeView, but under the root item.
' We reparent the nodes in the next step
'
       Do Until rsSections.EOF
           sParent = rsSections("ParentID").Value
           sKey = rsSections("ID").Value
           sText = rsSections("Description").Value
           Set nNode = .Add("ROOT", tvwChild, "C" & sKey, sText, "FOLDER")
'
' Record parent ID
'
           nNode.Tag = "C" & sParent
           rsSections.MoveNext
       Loop
   
   End With
'
' Here's where we rebuild the structure of the nodes
'
   For Each nNode In tvCodeItems.Nodes
       sParent = nNode.Tag
       If Len(sParent) > 0 Then        ' Don't try and reparent the ROOT !
           If sParent = "C0" Then
               sParent = "ROOT"
           End If
           Set nNode.Parent = tvCodeItems.Nodes(sParent)
       End If
   Next
'
' Now setup the images for each node in the treeview & set each node to
' be sorted if it has children
'
   For Each nNode In tvCodeItems.Nodes
       If nNode.Children = 0 Then
           nNode.Image = "CHILD"
       Else
           nNode.Sorted = True
       End If
   Next
   
   Set rsSections = Nothing
'
' Expand the Root Node
'
   tvCodeItems.Nodes("ROOT").Sorted = True
   tvCodeItems.Nodes("ROOT").Expanded = True
   
   TreeRedraw tvCodeItems.hwnd, True
   Exit Sub

vbErrorHandler:
   TreeRedraw tvCodeItems.hwnd, True
   MsgBox Err.Number & " " & Err.Description & " " & Err.Source
End Sub

Private Sub TreeRedraw(ByVal lHwnd As Long, ByVal bRedraw As Boolean)
   SendMessageLong lHwnd, WM_SETREDRAW, bRedraw, 0
End Sub

************************




all agree that this solves it for me? (i agree, b/c it did exactly what i wanted...)

ameba, I hadn't heard of this technique before.  Is it faster to load this way?  Or does is just simplify retrieving the data so that you don't have to order the recordset with all children following their parents.
Paurths,

What advantage is there to recreate the function/contents of the DirectoryList box?  Messing with a Treeview control is slower and performs unnecessary to iterate/add-node for a directory the user isn't interested in seeing.
Avatar of Paurths

ASKER

that is correct aikimark,

but there wont be no users using this program.
its for my use only.
comment of Date: 03/06/2002 03:47PM PST
****************************
What i would like to do is:
if i could fill a treeview with all the folders, click on a node (e.g. a folder on a shared folder)
immediatly know what groups, users, have what permissions.
I know i can retrieve this information in my form where the folders are shown/added.
I was just wondering if it can be done. (i would probably be the only person on this planet using this
program, but i figured it would be good excercice...)
*****************************

since the database will hold all the users, global groups, local groups, folders, permissions on folders it would / could give me a quick view 'who can do what' when i click a folder.
Sometimes when i receive a call from any user of our network asking me why on earth (s)he can not write/delete/access a certain folder i need to look up permissions. The way it is done now takes too long.
with a click on folder in the treeview i want to see why this user calls me.
In the end there wont be that many folders in this database.
Only the folders where the shared data is, will be present. (for several servers tho) So i'm not talking about a couple of thousand folders. (perhaps in the future)
Since the security only goes 3 or 4 levels deep, this must be managable.
If a user adds a folder at level e.g. 7 just for her/his sake i dont need that in the database. (for now)


like i said, there might be programs out there that have a solution for this, but i really want a total program where i can also place computers on desks. (using Visio for the graphical interface of the building / offices --> is build automatically on the click of a button, nice tool, when a desk gets moved i give it other co-ordinates in the program and it will show up at the correct place in the graphical overview --> once it is finished that is!)
connect computers to outlets, the outlets to the swichtes,
desks have several outlets at their disposal, so u can connect computers to a desk/outlet, printers and printservers.
there are several other issues in it, but i cant name them all or i'll fill at least 3 pages here...

It is big...

cheers
Ricky

ameba,

I tried the code above but the declaration for the API SendMessageLong is missing.  I tried pasting SendMessage from the API text viewer, re-aliasing it to SendMessageLong.  I also set the lParam value to ByVal as this sometimes causes problems if it's passed ByRef, but still the call is appearantly failing.

What happens is that the tree keeps trying to redraw, and this makes the process incredibly slow.  The SendMessageLong returns a zero, so, it looks like it works...

I tried every combination I could think of for SendMessage and still no luck.  Finally, I found an alternative API for LockWindowUpdate.  This worked perfectly.


'Private Declare Function SendMessageLong Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
'Private Const WM_SETREDRAW = &HB

Private Declare Function LockWindowUpdate Lib "user32" (ByVal hwndLock As Long) As Long

Private Sub TreeRedraw(ByVal lHwnd As Long, ByVal bRedraw As Boolean)

'    SendMessageLong lHwnd, WM_SETREDRAW, bRedraw, 0
'    SendMessageLong lHwnd, WM_SETREDRAW, bRedraw, &O0
'    SendMessageLong lHwnd, WM_SETREDRAW, bRedraw, 0&
'    SendMessageLong lHwnd, WM_SETREDRAW, bRedraw, ByVal 0&
'    SendMessageLong lHwnd, WM_SETREDRAW, -CLng(bRedraw), 0&
'    SendMessageLong lHwnd, WM_SETREDRAW, Abs(CLng(bRedraw)), 0&

   
    If bRedraw Then
        LockWindowUpdate 0
    Else
        LockWindowUpdate lHwnd
    End If
End Sub
Avatar of Paurths

ASKER

there is more,

'BoldTreeNode tvCodeItems.Nodes("ROOT")
is replaced by (b/c VB6)
tvCodeItems.Nodes("ROOT").Bold = True
       
Avatar of Paurths

ASKER

i guess its time to close this one.

any suggestions?

cheers
Ricky
Despite the slight omissions in the code, I think ameba's answer fits your needs.  It also taught me a new and valuable trick.  I've often gone to great lengths to try to order my rows in a hierarchical self-referencing table in order to add them to the tree in the proper order, and now I know that I can add them in any order and then re-parent them.  It's orders of magnitude faster than whatever I've done before.... so, it's a technique well worth the points.
Avatar of Paurths

ASKER

have already given ameba the points for his solution on 03/07/2002.
https://www.experts-exchange.com/jsp/qManageQuestion.jsp?ta=visualbasic&qid=20274655

cheers
Ricky
Paurths,

What are you going to do with THIS question?

Please close this question if we've answered it.  If you have questions, please consult the Community Support TA and ask them what you should do.
Paurths,

It's important to award the point to the expert inside of the question.  Because then the question gets saved to the Previously Asked Questions area and someone else will eventually "buy" the question and get the benefit of the excellent advice you've received here.  If you award the points in some other question without the technical stuff, then this question ends up deleted and then all of the good advice gets lost for others.......
Did you leave someone out?
If not, you will probably need to lower your points from 200 to 150 to be evenly divisible into three 50-point slices.
Points refunded and reduced to 50 for a point split.  Now you can accept an experts comment as an answer and make questions in this topic area for the other experts.

Computer101
E-E Moderator
Oh, sorry, I didn't participate in this thread.
- the function I suggested in another thread is not recursive, so I didn't want to jump in here.

The sample is somewhere on codeguru, but I don't have the exact link, and it works only for version 5 of the Common Controls.


mdougan,

> I tried the code above but the declaration for the API SendMessageLong is missing

You have the right version of SendMessageLong, but it is possible it doesn't work with version 6 of the TreeView.
LockWindowUpdate can work OK in most cases.  Or if there are not too many items, "tv.Visible = False" can work.

I don't use Treeview often, but for Listview, I got best results when sending WM_SETREDRAW message to PARENT FORM:

' from caList class
Public Property Let Redraw(ByVal Redraw As Boolean)
    Dim rc As RECT, ret As Long
   
    If Redraw Then
        Call SendMessage(ListView.Parent.hwnd, WM_SETREDRAW, REDRAWON, 0&)
        ' this is a bit less flicker
        ret = GetClientRect(ListView.hwnd, rc)
        ret = InvalidateRect(ListView.hwnd, rc, 0&)
    Else
        Call SendMessage(ListView.Parent.hwnd, WM_SETREDRAW, REDRAWOFF, 0&)
    End If
End Property