Results 1 to 8 of 8

Thread: Async Treeview

  1. #1

    Thread Starter
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    Async Treeview

    Hi,

    I am using the following code to add all files and sub-directories to a Treeview control. The user can select a directory (using a folder browser dialog) and this code will add that directory, and all it's files and sub-directories, to the Treeview:

    vb.net Code:
    1. Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    2.         Using fbd As New FolderBrowserDialog
    3.             If fbd.ShowDialog = Windows.Forms.DialogResult.OK Then
    4.  
    5.                 Dim n As New TreeNode(IO.Path.GetFileName(fbd.SelectedPath))
    6.                 n.Tag = fbd.SelectedPath
    7.                 n.Name = fbd.SelectedPath
    8.  
    9.                 TreeView1.Nodes.Add(n)
    10.                 PopulateTreeview(n)
    11.  
    12.             End If
    13.         End Using
    14.     End Sub
    15.  
    16.     Public Sub PopulateTreeview(ByVal node As TreeNode)
    17.        
    18.         'Add directories
    19.         Dim dirInfo As New IO.DirectoryInfo(node.Name)
    20.         For Each d As IO.DirectoryInfo In dirInfo.GetDirectories
    21.             Dim n As New TreeNode(d.Name)
    22.             n.Tag = d.FullName
    23.             n.Name = d.FullName
    24.  
    25.             'Resursively add sub-directories
    26.             PopulateTreeview(n)
    27.             node.Nodes.Add(n)
    28.         Next
    29.  
    30.         'Add files
    31.         For Each f As IO.FileInfo In dirInfo.GetFiles
    32.             Dim n As New TreeNode(f.Name)
    33.             n.Tag = f.FullName
    34.             n.Name = f.FullName
    35.  
    36.             node.Nodes.Add(n)
    37.         Next
    38.     End Sub


    I have noticed now that it is now extremely fast however. I would like the actual updating of the treeview to happen in a background thread so the UI doesn't lock up. But I've never done any threading so I need some help...

    I have tried a few ways, mainly 'copying' the code from this recent codebank submission. However, I keep getting the obvious cross-thread call issue that I am adding nodes to the 'node' parameter, which is not allowed.

    I am at a loss at how to do this otherwise. How can I update a Treeview like this if I have to recursively add nodes to the 'node' parameter, which is not 'available' in a separate thread?

    Thanks!

  2. #2
    PowerPoster Jenner's Avatar
    Join Date
    Jan 2008
    Location
    Mentor, OH
    Posts
    3,712

    Re: Async Treeview

    Try something like this. I broke the populator into it's own class as well, but you can do it in other ways:

    Code:
    Public Class Form1
        Private Delegate Function NodeAddDelegate(ByVal n1 As TreeNode, ByVal n2 As TreeNode) As Boolean
    
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Using fbd As New FolderBrowserDialog
                If fbd.ShowDialog = Windows.Forms.DialogResult.OK Then
    
                    Dim n As New TreeNode(IO.Path.GetFileName(fbd.SelectedPath))
                    n.Tag = fbd.SelectedPath
                    n.Name = fbd.SelectedPath
    
                    TreeView1.Nodes.Add(n)
                    Dim Tvp As New TreeviewPopulator With {.Node = n, .NodeFunction = AddressOf NodeAdd}
                    Dim t As New Threading.Thread(AddressOf Tvp.ThreadStart)
                    t.IsBackground = True
                    t.Start()
    
                End If
            End Using
        End Sub
    
        Private Function NodeAdd(ByVal n1 As TreeNode, ByVal n2 As TreeNode) As Boolean
            If TreeView1.InvokeRequired Then
                Dim d As New NodeAddDelegate(AddressOf NodeAdd)
                TreeView1.Invoke(d, New Object() {n1, n2})
            Else
                n1.Nodes.Add(n2)
            End If
        End Function
    End Class
    
    Public Class TreeviewPopulator
        Private n As TreeNode
        Private fn As Func(Of TreeNode, TreeNode, Boolean)
    
        Public Property NodeFunction() As Func(Of TreeNode, TreeNode, Boolean)
            Get
                Return fn
            End Get
            Set(ByVal value As Func(Of TreeNode, TreeNode, Boolean))
                fn = value
            End Set
        End Property
    
        Public Property Node() As TreeNode
            Get
                Return n
            End Get
            Set(ByVal value As TreeNode)
                n = value
            End Set
        End Property
    
        Public Sub ThreadStart()
            PopulateTreeview(n)
        End Sub
    
        Private Sub PopulateTreeview(ByVal node As TreeNode)
            'Add directories
            Dim dirInfo As New IO.DirectoryInfo(node.Name)
            For Each d As IO.DirectoryInfo In dirInfo.GetDirectories
                Dim n As New TreeNode(d.Name)
                n.Tag = d.FullName
                n.Name = d.FullName
    
                'Resursively add sub-directories
                PopulateTreeview(n)
                fn(node, n)
            Next
    
            'Add files
            For Each f As IO.FileInfo In dirInfo.GetFiles
                Dim n As New TreeNode(f.Name)
                n.Tag = f.FullName
                n.Name = f.FullName
                fn(node, n)
            Next
        End Sub
    End Class
    My CodeBank Submissions: TETRIS using VB.NET2010 and XNA4.0, Strong Encryption Class, Hardware ID Information Class, Generic .NET Data Provider Class, Lambda Function Example, Lat/Long to UTM Conversion Class, Audio Class using BASS.DLL

    Remember to RATE the people who helped you and mark your forum RESOLVED when you're done!

    "Two things are infinite: the universe and human stupidity; and I'm not sure about the universe. "
    - Albert Einstein

  3. #3
    Pro Grammar chris128's Avatar
    Join Date
    Jun 2007
    Location
    England
    Posts
    7,604

    Re: Async Treeview

    Thanks for that Jenner, I'm going to put a link to this thread in my codebank thread that Nick mentioned as the guy asking questions in there wanted to know how to build the nodes in an Async method
    My free .NET Windows API library (Version 2.2 Released 12/06/2011)

    Blog: cjwdev.wordpress.com
    Web: www.cjwdev.co.uk


  4. #4

  5. #5

    Thread Starter
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    Re: Async Treeview

    Ok one more thing. I was hoping I could do this myself but apparently not.

    I also want to add images to my Treeview. The images are loaded from the system so they are the same. I used the following code for this:
    vb.net Code:
    1. Public Function GetImageInIML(ByVal path As String, ByVal iml As ImageList) As Integer
    2.         Dim imgPtr As IntPtr = GetImageHandle(path).hIcon
    3.         If Not iml.Images.ContainsKey(imgPtr.ToString) Then iml.Images.Add(imgPtr.ToString, Drawing.Icon.FromHandle(imgPtr))
    4.         Dim i As Integer = iml.Images.IndexOfKey(imgPtr.ToString)
    5.  
    6.         Return i
    7.     End Function
    I simply call this method before I add a node to the treeview, and set the ImageIndex and SelectedImageIndex to the return value:
    vb.net Code:
    1. Dim i As Integer = GetImageInIML(d.FullName, Me.ImageList)
    2.             n.ImageIndex = i
    3.             n.SelectedImageIndex = i
    The GetImageHandle function is the function that retrieves the system image from the filename; I don't think it's relevant here.


    The problem of course now is that I also get the cross thread call exception when I try to add an image to the Imagelist. So, I thought I could just do the same as you did with the nodes, create a delegate and 'Invoke' the adding of the image...

    But that doesn't work. First of all, an Imagelist doesn't seem to have an InvokeRequired property, nor an Invoke method... ? I suppose this is because an Imagelist is a component rather than a control, but how do I do this then?

  6. #6
    Pro Grammar chris128's Avatar
    Join Date
    Jun 2007
    Location
    England
    Posts
    7,604

    Re: Async Treeview

    Quote Originally Posted by NickThissen View Post
    Works great, nice! I wish I understood it completely lol... What does the 'fn' and the Func(Of ..) do? Does this have something to do with lambda thingy ..?
    I'd be keen to see an explanation of that too as I've not come accross that before..
    My free .NET Windows API library (Version 2.2 Released 12/06/2011)

    Blog: cjwdev.wordpress.com
    Web: www.cjwdev.co.uk


  7. #7
    PowerPoster Jenner's Avatar
    Join Date
    Jan 2008
    Location
    Mentor, OH
    Posts
    3,712

    Re: Async Treeview

    You can do it in the same function that you're adding the node to the TreeView. The invoke doesn't have to come from the Imagelist. In actuality, it can come from anything created on the thread the Imagelist was created under:

    Code:
        Private Function NodeAdd(ByVal n1 As TreeNode, ByVal n2 As TreeNode) As Boolean
            If TreeView1.InvokeRequired Then
                Dim d As New NodeAddDelegate(AddressOf NodeAdd)
                TreeView1.Invoke(d, New Object() {n1, n2})
            Else
                'This code will always be executed by the primary thread
                Dim i As Integer = GetImageInIML(n1.Name, Me.ImageList)
                n1.ImageIndex = i
                n1.SelectedImageIndex = i
                n1.Nodes.Add(n2)
            End If
        End Function
    Think of Invoke as a tiny, momentary "timeout" period, where the invoking item stops listening to it's own thread for instructions and listens to the delegate. The delegate is like a petitioner.

    You can for example, call Invoke from the Form itself. Or from the TreeView.

    The Func is an example of a delegate that encapsulates a method. A pure lambda would be if I defined the function that it is a delegate to:
    Code:
        Dim add1 As Func(Of Integer, Integer) = Function(num As Integer) num + 1
        MessageBox.Show(add1(5).ToString)
        'Message Box shows 6
    LINQ uses a lot of lambdas behind the scenes and under the hood.
    My CodeBank Submissions: TETRIS using VB.NET2010 and XNA4.0, Strong Encryption Class, Hardware ID Information Class, Generic .NET Data Provider Class, Lambda Function Example, Lat/Long to UTM Conversion Class, Audio Class using BASS.DLL

    Remember to RATE the people who helped you and mark your forum RESOLVED when you're done!

    "Two things are infinite: the universe and human stupidity; and I'm not sure about the universe. "
    - Albert Einstein

  8. #8

    Thread Starter
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    Re: Async Treeview

    Thanks.

    It does work, but for some reason it is still locking up the UI thread now. I think it's the GetImageHandle function that does this. I can imagine that it takes a little time to get an image from a filename, and while it's doing that the UI still locks up

    I'm not sure if I can do anything about this...


    Here is the full code so far, so you can try it for yourself:
    Code:
    Option Strict On
    
    Imports System.Runtime.InteropServices
    
    Public Class Form1
    
    #Region " Get System Images "
    
        Public Function GetImageInIML(ByVal path As String, ByVal iml As ImageList) As Integer
            Dim imgPtr As IntPtr = GetImageHandle(path).hIcon
            If Not iml.Images.ContainsKey(imgPtr.ToString) Then iml.Images.Add(imgPtr.ToString, Drawing.Icon.FromHandle(imgPtr))
            Dim i As Integer = iml.Images.IndexOfKey(imgPtr.ToString)
    
            Return i
        End Function
    
        Public Structure SHFILEINFO
            Public hIcon As IntPtr            ' : icon
            Public iIcon As Integer           ' : icondex
            Public dwAttributes As Integer    ' : SFGAO_ flags
            <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)> _
            Public szDisplayName As String
            <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=80)> _
            Public szTypeName As String
        End Structure
    
        'Retries windows icon from filename
        Public Declare Auto Function SHGetFileInfo Lib "shell32.dll" _
                (ByVal pszPath As String, _
                 ByVal dwFileAttributes As Integer, _
                 ByRef psfi As SHFILEINFO, _
                 ByVal cbFileInfo As Integer, _
                 ByVal uFlags As Integer) As IntPtr
    
        Public Const SHGFI_ICON As Int32 = &H100
        Public Const SHGFI_SMALLICON As Int32 = &H1
        Public Const SHGFI_LARGEICON As Int32 = &H0    ' Large icon
        Public nIndex As Int32 = 0
    
        Public Function GetImageHandle(ByVal file As String) As SHFILEINFO
            Dim shinfoSmall As New SHFILEINFO()
            shinfoSmall.szDisplayName = New String(Chr(0), 260)
            shinfoSmall.szTypeName = New String(Chr(0), 80)
    
            Dim hImgSmall As IntPtr = SHGetFileInfo(file, 0, shinfoSmall, _
                Marshal.SizeOf(shinfoSmall), SHGFI_ICON Or SHGFI_SMALLICON)
    
            Return shinfoSmall
        End Function
    
    #End Region
    
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            Using fbd As New FolderBrowserDialog
                If fbd.ShowDialog = Windows.Forms.DialogResult.OK Then
    
                    Dim n As New TreeNode(IO.Path.GetFileName(fbd.SelectedPath))
                    n.Tag = fbd.SelectedPath
                    n.Name = fbd.SelectedPath
    
                    Dim i As Integer = GetImageInIML(fbd.SelectedPath, iml)
                    n.ImageIndex = i
                    n.SelectedImageIndex = i
    
                    TreeView1.Nodes.Add(n)
                    Dim Tvp As New TreeviewPopulator With {.Node = n, .NodeFunction = AddressOf NodeAdd}
                    Dim t As New Threading.Thread(AddressOf Tvp.ThreadStart)
                    t.IsBackground = True
                    t.Start()
    
                End If
            End Using
        End Sub
    
        Private Delegate Function NodeAddDelegate(ByVal n1 As TreeNode, ByVal n2 As TreeNode) As Boolean
    
        Private Function NodeAdd(ByVal n1 As TreeNode, ByVal n2 As TreeNode) As Boolean
            If TreeView1.InvokeRequired Then
                Dim d As New NodeAddDelegate(AddressOf NodeAdd)
                TreeView1.Invoke(d, New Object() {n1, n2})
            Else
                Dim i As Integer = GetImageInIML(n2.Name, Me.iml)
                n2.ImageIndex = i
                n2.SelectedImageIndex = i
    
                n1.Nodes.Add(n2)
            End If
        End Function
    
    End Class
    
    Public Class TreeviewPopulator
        Private n As TreeNode
        Private fn As Func(Of TreeNode, TreeNode, Boolean)
    
        Public Property NodeFunction() As Func(Of TreeNode, TreeNode, Boolean)
            Get
                Return fn
            End Get
            Set(ByVal value As Func(Of TreeNode, TreeNode, Boolean))
                fn = value
            End Set
        End Property
    
        Public Property Node() As TreeNode
            Get
                Return n
            End Get
            Set(ByVal value As TreeNode)
                n = value
            End Set
        End Property
    
        Public Sub ThreadStart()
            PopulateTreeview(n)
        End Sub
    
        Private Sub PopulateTreeview(ByVal node As TreeNode)
            'Add directories
            Dim dirInfo As New IO.DirectoryInfo(node.Name)
            For Each d As IO.DirectoryInfo In dirInfo.GetDirectories
                Dim n As New TreeNode(d.Name)
                n.Tag = d.FullName
                n.Name = d.FullName
    
                'Resursively add sub-directories
                PopulateTreeview(n)
                fn(node, n)
            Next
    
            'Add files
            For Each f As IO.FileInfo In dirInfo.GetFiles
                Dim n As New TreeNode(f.Name)
                n.Tag = f.FullName
                n.Name = f.FullName
    
                fn(node, n)
            Next
        End Sub
    End Class
    It doesn't lock up completely, but just momentarily while it is loading the image. If you put an animated gif on the form you can see that it becomes 'choppy' when each image is loaded

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width