|
-
May 20th, 2009, 11:20 AM
#1
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:
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) PopulateTreeview(n) End If End Using End Sub Public 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) node.Nodes.Add(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 node.Nodes.Add(n) Next 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!
-
May 20th, 2009, 12:38 PM
#2
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
-
May 20th, 2009, 03:13 PM
#3
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
-
May 21st, 2009, 05:49 AM
#4
Re: Async Treeview
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 ..?
-
May 21st, 2009, 06:23 AM
#5
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:
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
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:
Dim i As Integer = GetImageInIML(d.FullName, Me.ImageList) n.ImageIndex = i 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?
Last edited by NickThissen; May 21st, 2009 at 06:27 AM.
-
May 21st, 2009, 07:32 AM
#6
Re: Async Treeview
 Originally Posted by NickThissen
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..
-
May 21st, 2009, 07:44 AM
#7
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.
-
May 21st, 2009, 07:57 AM
#8
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
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|