Sunday, January 6, 2013

TreeView with recursivity

Fill a TreeView with recursivity


I decided to put in 1 single place an article to help build a treeview with recursivity.

There is a lot of forum or Question and Answser related on TreeView but i think is a good idea to write 1 article on the subject.

For many people, anything that works with node is a little bit difficult. The structure itself is not easy. For example, xml file a compose of node. If you don't like to work with node, you will probably choose to work with text file. Text File are simple. They work line by line. So is very sequential.

TreeView work the same way. They use node to structure his own databse. Each node have a parent. That means that the Node can't exist alone.




TreeView are composed of nodes after nodes. If your TreeView represent a kind of explorer, you might have several types of object such as folder and files.

In this post, I have to give you a warning. I am not interested to vomit the answer. Instead, I’ll try to explain how I could reach the reach the answer so eventually; you could understand how I was able to build the code without any copy paste from any forum or blog over the internet.  This post might not for you. If not, you will immediately go straight to the answer at the far end of this article. You copy and paste the code and voilà. Here is the link: [Search File - Improve performance with Recursivity]

If you force yourself to read this article, please note I will separate the article into 4 parts

  1. Basic recursive search file 
  2. Analyzing the needs 
  3. Implementing the changes
  4. Run the code



To be honest, I can’t remember making any treeview for a Windows Start Menu in my lifetime. I never Bing’d or Google’d for the answer. I simply applied a logical pathway to create the code I want less than 10 minutes. Yes, under 10 minutes with music on my headphone. Anyway.

Windows 7 Start menu




Part 1 : Basic recursive search file


I pick a code from one of my previous post : search file search file - Improve performance . In that post a made a recursive function to search file file and directory over and over. All the result are injected in a ListView and not in a TreeView. Enventually, we will have to adapt the code because a ListView and a TreeView works differently.

While ListView group the items in a collection, Treeview are grouping the data with nodes.

Please take a look at the code and go to the next post.



    Private Sub findfiles()

        Dim dirPath As String
        Dim path_valid As Boolean
        path_valid = True

        dirPath = "c:\temp\" ' temp = no access problem
        dirPath = "c:\System Recovery" ' System Recovery = access problem




        '----------------------------------------------
        ' CHECK PATH IF VALID
        '----------------------------------------------
        'trim the sPath to remove space before and after the String
        dirPath = Trim(dirPath)

        'check if sPath is not zero length
        If Len(dirPath) > 0 Then

        Else
            path_valid = False
        End If

        'check for invalid char
        ' \ * | : ? < > / except the \ because is a directory seperator
        ' and * that means to ignore whatever inside the other chars. (optional)
        If InStr(dirPath, "|", CompareMethod.Binary) > 0 Then
            path_valid = False
        End If
        If InStr(dirPath, ">", CompareMethod.Binary) > 0 Then
            path_valid = False
        End If
        If InStr(dirPath, "<", CompareMethod.Binary) > 0 Then
            path_valid = False
        End If
        If InStr(dirPath, "?", CompareMethod.Binary) > 0 Then
            path_valid = False
        End If
        If InStr(dirPath, ":", CompareMethod.Binary) = 2 Then
            'very basic way to check if there is a ":" in the Path.
            Dim str_temp As String = dirPath.Substring(2, Len(dirPath) - 2)
            If InStr(str_temp, ":", CompareMethod.Binary) > 0 Then
                path_valid = False
            End If

        End If
        If InStr(dirPath, "*", CompareMethod.Binary) > 0 Then
            path_valid = False
        End If
        'be careful with non-english caracters


        '----------------------------------------------
        ' SECURITY
        '----------------------------------------------
        Dim oDirectorySecurity As System.Security.AccessControl.DirectorySecurity
        Dim oAuthorizationRuleCollection As System.Security.AccessControl.AuthorizationRuleCollection
        Dim oRule As System.Security.AccessControl.FileSystemAccessRule
        Dim Security_valid As Boolean

        Security_valid = False
        oDirectorySecurity = IO.Directory.GetAccessControl(dirPath)
        oAuthorizationRuleCollection = oDirectorySecurity.GetAccessRules(True, True, Type.GetType("System.Security.Principal.NTAccount"))

        For Each oRule In oAuthorizationRuleCollection

            If oRule.FileSystemRights = Security.AccessControl.FileSystemRights.FullControl Then
                If InStr(oRule.IdentityReference.Value, My.User.Name) > 0 Then
                    Security_valid = True
                End If
            End If

        Next

        '----------------------------------------------
        ' SECURITY
        '----------------------------------------------
        Dim Is_Folder_shortcut As Boolean
        Is_Folder_shortcut = False
        If dirPath.StartsWith("c:\Users", True, System.Globalization.CultureInfo.CurrentCulture) Then
            'very basic way to force you to be very carefull with that folder
            Is_Folder_shortcut = True
        End If

        '----------------------------------------------
        ' IF ALL OK, THEN DISPLAY THE RESULT
        '----------------------------------------------
        If oForm2 Is Nothing OrElse oForm2.IsDisposed Then
            oForm2 = New Form2
            oForm2.Show()
        End If
        oForm2.ListBox1.Items.Clear()
        If path_valid = True And Security_valid = True And Is_Folder_shortcut = False Then

            For Each foundFile As String In My.Computer.FileSystem.GetFiles( _
                    dirPath, FileIO.SearchOption.SearchAllSubDirectories, "*.dll")

                oForm2.ListBox1.Items.Add(foundFile)

            Next
        Else
            MsgBox("path_valid =" & path_valid & vbCrLf & "Security_valid =" & Security_valid)
        End If

    End Sub



Now, this is a new code with minor change. All modifications are highlighted in yellow.


    Private Sub findfiles(Optional dirPath As String = "c:\temp\", Optional String_to_Search As String = "*.dll")

        'Dim dirPath As String ' you need to put in argument a "top" directory (optional if you want)
        Dim path_valid As Boolean
        path_valid = True

        'dirPath = "c:\temp\" ' temp = no access problem
        'dirPath = "c:\System Recovery" ' System Recovery = access problem




        '----------------------------------------------
        ' CHECK PATH IF VALID
        '----------------------------------------------
        'trim the sPath to remove space before and after the String
        dirPath = Trim(dirPath)

        'check if sPath is not zero length
        If Len(dirPath) > 0 Then

        Else
            path_valid = False
        End If

        'check for invalid char
        ' \ * | : ? < > / except the \ because is a directory seperator
        ' and * that means to ignore whatever inside the other chars. (optional)
        If InStr(dirPath, "|", CompareMethod.Binary) > 0 Then
            path_valid = False
        End If
        If InStr(dirPath, ">", CompareMethod.Binary) > 0 Then
            path_valid = False
        End If
        If InStr(dirPath, "<", CompareMethod.Binary) > 0 Then
            path_valid = False
        End If
        If InStr(dirPath, "?", CompareMethod.Binary) > 0 Then
            path_valid = False
        End If
        If InStr(dirPath, ":", CompareMethod.Binary) = 2 Then
            'very basic way to check if there is a ":" in the Path.
            Dim str_temp As String = dirPath.Substring(2, Len(dirPath) - 2)
            If InStr(str_temp, ":", CompareMethod.Binary) > 0 Then
                path_valid = False
            End If

        End If
        If InStr(dirPath, "*", CompareMethod.Binary) > 0 Then
            path_valid = False
        End If
        'be careful with non-english caracters


        '----------------------------------------------
        ' SECURITY
        '----------------------------------------------
        Dim oDirectorySecurity As System.Security.AccessControl.DirectorySecurity
        Dim oAuthorizationRuleCollection As System.Security.AccessControl.AuthorizationRuleCollection
        Dim oRule As System.Security.AccessControl.FileSystemAccessRule
        Dim Security_valid As Boolean

        Security_valid = False
        oDirectorySecurity = IO.Directory.GetAccessControl(dirPath)
        oAuthorizationRuleCollection = oDirectorySecurity.GetAccessRules(True, True, Type.GetType("System.Security.Principal.NTAccount"))

        For Each oRule In oAuthorizationRuleCollection

            If oRule.FileSystemRights = Security.AccessControl.FileSystemRights.FullControl Then
                If InStr(oRule.IdentityReference.Value, My.User.Name) > 0 Then
                    Security_valid = True
                End If
            End If

        Next

        '----------------------------------------------
        ' SECURITY
        '----------------------------------------------
        Dim Is_Folder_shortcut As Boolean
        Is_Folder_shortcut = False
        If dirPath.StartsWith("c:\Users", True, System.Globalization.CultureInfo.CurrentCulture) Then
            'very basic way to force you to be very carefull with that folder
            Is_Folder_shortcut = True
        End If

        '----------------------------------------------
        ' IF ALL OK, THEN DISPLAY THE RESULT
        '----------------------------------------------
        If oForm2 Is Nothing OrElse oForm2.IsDisposed Then
            oForm2 = New Form2
            oForm2.Show()
        End If
        'oForm2.ListBox1.Items.Clear() ' recursive search file : don't clear the listBox
        If path_valid = True And Security_valid = True And Is_Folder_shortcut = False Then

            For Each foundFile As String In My.Computer.FileSystem.GetFiles( _
                    dirPath, FileIO.SearchOption.SearchTopLevelOnly, String_to_Search) 'recursive search file : you need to limit your search to 1 single directory : and put extention in search argument

                oForm2.ListBox1.Items.Add(foundFile)

            Next

            For Each foundDirectory As String In My.Computer.FileSystem.GetDirectories(dirPath, FileIO.SearchOption.SearchTopLevelOnly)
                findfiles(foundDirectory) ''recursive search file : re-use "himself"
            Next
        Else
            MsgBox("path_valid =" & path_valid & vbCrLf & "Security_valid =" & Security_valid)
        End If

    End Sub







Part 2 : Analyzing the needs



This is the best part. You need to analyze your code. Try to understand what the function needs to do what you want. This is not an easy step. With practice, you could build you brain to code directly in Visual Studio.

Lets only focus on this part of the code:


For Each foundFile As String In My.Computer.FileSystem.GetFiles( _
      dirPath, FileIO.SearchOption.SearchTopLevelOnly, String_to_Search)

       oForm2.ListBox1.Items.Add(foundFile)

Next

For Each foundDirectory As String In My.Computer.FileSystem.GetDirectories( _ dirPath, FileIO.SearchOption.SearchTopLevelOnly)

      findfiles(foundDirectory) ''recursive search file : re-use "himself"

Next



We see that for every found file, it will inject the name inside the ListBox1. Simple right? Yes it is. Ok, What if we replace the ListBox1 for  TreeView ?



For Each foundFile As String In My.Computer.FileSystem.GetFiles( _
      dirPath, FileIO.SearchOption.SearchTopLevelOnly, String_to_Search)

       'oForm2.ListBox1.Items.Add(foundFile)
       TreeView1.Nodes.Add(foundFile)

Next


For Each foundDirectory As String In My.Computer.FileSystem.GetDirectories( _ dirPath, FileIO.SearchOption.SearchTopLevelOnly)

      findfiles(foundDirectory) ''recursive search file : re-use "himself"

Next


Congratulations, your treeview will get all the file names on the root directory. Now, what do you have to do to put the files from the sub-directories? You know that the 2nd loop is capable to get the finds the files from all subdirectories but your recursive function will work.

What do you want?
I want the TreeView to display the files under his directory using a recursive function.

What do you have now?
I have the ListBox that gets all the files and directories injected using a recursive function.

What your TreeView need to give what you want?
Need a file path and an active node.

Windows 7 File Explorer

Is your current recursive function having all the parameter for the TreeView?
The TreeView need the active node to put the files Names. The Nodes is part of the structure of the TreeNodes.


Is not really easy to put in words what you have in mind. Some people will see another way to get to the results. Some solutions are more efficient while others are more appropriate.





Part 3 : Implementing the changes




Ok, time to change the recursive function with the question you ask yourself. You may have to do some tries or test before getting something to work perfectly.  Normally, if you analyze your problem and ask yourself a lot of question, you should be able to make the changes easily.

Before :


Private Sub findfiles(Optional dirPath As String = "c:\temp\", Optional String_to_Search As String = "*.dll")



For Each foundFile As String In My.Computer.FileSystem.GetFiles( _
      dirPath, FileIO.SearchOption.SearchTopLevelOnly, String_to_Search)

       oForm2.ListBox1.Items.Add(foundFile)

Next

For Each foundDirectory As String In My.Computer.FileSystem.GetDirectories( _ dirPath, FileIO.SearchOption.SearchTopLevelOnly)

      findfiles(foundDirectory) ''recursive search file : re-use "himself"

Next




For Each foundFile As String In My.Computer.FileSystem.GetFiles( _
       dirPath, FileIO.SearchOption.SearchTopLevelOnly, String_to_Search) 'recursive search file : you need to limit your search to 1 single directory : and put extension in search argument

       'oForm2.ListBox1.Items.Add(foundFile)
       'TreeView1.Nodes.Add(foundFile)

       no1.Nodes.Add(foundFile)

Next

For Each foundDirectory As String In my.Computer.FileSystem.GetDirectories( dirPath, FileIO.SearchOption.SearchTopLevelOnly)

    'findfiles(foundDirectory) ''recursive search file : re-use "himself"
     Dim no2 As TreeNode
     no2 = no1.Nodes.Add(foundDirectory)
     findfiles(no2, foundDirectory, String_to_Search) ''recursive search file : re-use "himself"
Next



After:


Private Sub findfiles(no1 As TreeNode, Optional dirPath As String = "c:\temp\", Optional String_to_Search As String = "*.dll")


I added no1 for node one, this will represent the main node or current node. It works exactly the same way compared to dirPath. dirPath acts like the current path. All the stuff in the argument in a recursive function are constantly reused and is always base from the current data. The current data here in this example is the current node with a current directory.



For Each foundFile As String In My.Computer.FileSystem.GetFiles( _
       dirPath, FileIO.SearchOption.SearchTopLevelOnly, String_to_Search) 'recursive search file : you need to limit your search to 1 single directory : and put extension in search argument

       'oForm2.ListBox1.Items.Add(foundFile)
       'TreeView1.Nodes.Add(foundFile)

       no1.Nodes.Add(foundFile)

Next

For Each foundDirectory As String In my.Computer.FileSystem.GetDirectories( dirPath, FileIO.SearchOption.SearchTopLevelOnly)

    'findfiles(foundDirectory) ''recursive search file : re-use "himself"
     Dim no2 As TreeNode
     no2 = no1.Nodes.Add(foundDirectory)
     findfiles(no2, foundDirectory, String_to_Search) ''recursive search file : re-use "himself"
Next




And here how I decided to start my findfiles function. I injected a first node called MAIN.


    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        'position()
        'Fill_TreeView()
        Me.TreeView1.Nodes.Add("MAIN")
        Dim no As TreeNode
        no = Me.TreeView1.Nodes.Item(0)
        findfiles(no, "C:\ProgramData\Microsoft\Windows\Start Menu", "*.lnk")
    End Sub






Part 4 : Running the Code



Let’s remember I made a sample of a Windows 8 Start menu using VB.NET with Visual Studio 2010.  I was doing the program in the menu. Is not something hard, is just logic and simple if you carefully think of what are you doing.

Run the Visual Basic code when you are ready. Here you want to fill a TreeView. Here is the final result:

treeview in Windows 8 Start Menu

Of course, the TreeView is terribly ugly. The list is upside down. There are no icons. The text is small and the whole path is displayed.

I guest you might do the same exercises, check for your existing code and find what is missing.





About 

I invite you to visit my blog for more articles and leave a comment. Check Technologies represents more than 10 years .... Computer and computer aided design.

Check Technologies ltée

235 Adrien-Provencher
Beloeil, (Québec), J3G 0C8, Canada
Tel : 514-705-7690
emal: 
info@checktechno.ca



No comments:

Post a Comment