|
-
Oct 28th, 2015, 03:59 PM
#1
Thread Starter
Member
Populate Tree View w/ Progress Bar (Background Worker? Multithreading?)
Hi guys,
I've tried all sorts of tutorials, research etc to get this working but have completely run out of ideas now so really need your help.
I have a TreeView which I populate with a list of files, however this can take quite a while sometimes, depending on how many files you have.
Code:
Public Sub GetFilesAndFolders(ByVal fileDirectory As String, ByVal parentNode As TreeNode)
Try
'Get list of all sub folders
Dim lstrFolders() As String = IO.Directory.GetDirectories(fileDirectory)
'If this folder has some folders we need to parse them into the treeview
Dim lobjChildNode As TreeNode = Nothing
If lstrFolders.Count <> 0 Then
For Each ldirFolder In lstrFolders
'Setup child node
lobjChildNode = New TreeNode
lobjChildNode.ImageIndex = 0
lobjChildNode.SelectedImageIndex = 0
lobjChildNode.Text = Path.GetFileName(ldirFolder)
lobjChildNode.Name = Path.GetFileName(ldirFolder)
'Add node to parent
parentNode.Nodes.Add(lobjChildNode)
'Parse this file/folder
GetFilesAndFolders(ldirFolder, lobjChildNode)
Next
Else 'it's a file
Dim lstrFiles() As String = IO.Directory.GetFiles(fileDirectory)
For Each file In lstrFiles
'We only care about sound files
If IsAudioFile(file) Then
Dim lobjNewNode As ctlMP3FileNode = New ctlMP3FileNode
lobjNewNode.filePath = file
lobjNewNode.id3Info = New ID3TagLibrary.MP3File(file)
lobjNewNode.Text = lobjNewNode.id3Info.Title
'if no title in id3 tag we don't want a node with no text
If lobjNewNode.Text = "" Then
lobjNewNode.Text = Path.GetFileName(file)
End If
lobjNewNode.ImageIndex = 1
lobjNewNode.SelectedImageIndex = 1
lobjNewNode.ContextMenuStrip = rcmFile
Dim name As String = lobjNewNode.id3Info.Title & lobjNewNode.id3Info.Artist
lobjNewNode.Name = Replace(name, " ", "")
parentNode.Nodes.Add(lobjNewNode)
'GetFilesAndFolders(file, lobjChildNode, audioContextMenu)
End If
Next
End If
Catch ProbablyNotAFolder As IOException
'ignore it
Catch AccessIssues As UnauthorizedAccessException
parentNode.Nodes.Add(AccessIssues.Message)
Catch ex As Exception
Throw New Exception(ex.Message)
End Try
End Sub
Now this works fine, but I really need to show a progress bar so the user knows the application is still responding.
I have tried putting this into a Background Worker and a different thread but always get a cross thread error, and I've tried Me.CheckForIllegalCrossThreads = False and Control.CheckForIllegalCrossThreads = False
Unfortunately because of the many different thinks I have tried I don't have any code around this to show, if someone could explain with some snippets how I could do this, that would be great.
If you need any further information I will get back to you ASAP.
Any advice is greatly appreciated, thanks in advance!
Last edited by iProRyan; Oct 28th, 2015 at 05:26 PM.
Reason: Some errors in my function to populate treeview I forgot to correct after editing
-
Oct 28th, 2015, 09:52 PM
#2
Re: Populate Tree View w/ Progress Bar (Background Worker? Multithreading?)
 Originally Posted by iProRyan
I have tried putting this into a Background Worker and a different thread but always get a cross thread error
Then you did it wrong, probably by not reading up on how a BackgroundWorker actually works. The DoWork event handler is where the background work is done. Anything that involves the UI is inherently NOT background work. It is, by definition, foreground work. You can't add a node to a TreeView in the DoWork event handler because that is not background work, hence the error.
What you should be doing is just creating all the nodes in the DoWork event handler. Instead of adding the top-level nodes to the Nodes collection of the TreeView, add then to a List(Of TreeNode). You can add the child nodes to the parent nodes without issue. At the end, you assign that List to the e.Result property and then you can get it back in the RunWorkerCompleted event handler. That is executed on the UI thread, so you can call AddRange to add all your top-level nodes and all their descendants to the TreeView in one batch.
As for progress, you can't report what you can't calculate. If you can calculate the actual progress then you can call ReportProgress and update the UI in the ProgressChanged event handler. If you can't calculate the actual proportion of the work done then a Marquee ProgressBar is probably the way to go.
Follow the CodeBank link in my signature below and check out my thread on Using The BackgroundWorker for some examples.
-
Oct 29th, 2015, 09:23 AM
#3
Re: Populate Tree View w/ Progress Bar (Background Worker? Multithreading?)
I did this exact thing a couple of years ago and let's get something uncomfortable out of the way: it takes roughly as long to count the files so your progress bar is accurate as it does to just do the thing you're doing. I wrote an algorithm to count the files and it took 10 minutes with that algorithm and 5 minutes without it, I really liked having my 5 minutes back so I did something different. We'll get back to that.
This is a difficult problem, because you need to update the UI from the main thread. As JMC pointed out, if you got cross-threading errors with a BackgroundWorker you used it wrong, but that's no big deal. Let's talk about it conceptually, first.
BackgroundWorker code runs on two different threads. The OnDoWork() method and DoWork event handler both run on the worker thread. You are absolutely not allowed to do anything that directly touches a control from this thread. The ProgressChanged and RunWorkerCompleted events run on the UI thread. It is safe to work with controls from that thread.
So when you write BackgroundWorker code, you have to ask a few questions first. Here's my checklist.
- Do I need to update any controls? If not, I don't need to worry.
- If I need to update controls, is it a one-time update when I'm finished? If so, I need to use RunWorkerCompleted.
- If it's multiple updates as the process goes on, then I need to use both ProgressChanged and ReportProgress().
ReportProgress() is sort of weird if you haven't done it much. Here's how it works.
First, remember to set the WorkerReportsProgress property to true on your BackgroundWorker. If you don't, it won't raise ProgressChanged. I can't tell you how many times I've forgotten.
You write your code in DoWork/OnDoWork() and it runs until it reaches some point where it wants to send something to the UI. When that happens, you call BackgroundWorker.ReportProgress(). Consider the overloads. The one that just takes an Integer is fine for just updating a progress bar, but you also want to create TreeViewItems and add them to the tree. So what you want to use is the one that takes an Integer and an Object, and the Object you pass should be whatever you need to create the item. Do NOT create TreeViewItems here: they are technically controls, and must be created on the same thread as the TreeView. That might be what bit you last time. The most natural thing to do, in my opinion, is create your own custom class to pass back. But, in a hurry, I've passed DirectoryInfo and FileInfo objects here. When you call ReportProgress(), the ProgressChanged event is raised on the UI thread. The object you passed is in the EventArgs's UserState property, don't forget to cast to the type you need!
So, in summary:
- Never, ever, do anything that touches the TreeView or TreeViewItems inside DoWork.
- To send things to the UI thread so you CAN touch the TreeView or TreeViewItems, it's best to use ReportProgress(Integer, Object).
- When you call ReportProgress(), it will raise ProgressChanged on the UI thread. Get your stuff out of e.UserState and now you're free to use the UI thread.
There's one teeny thing left here: if you send a LOT of updates very frequently, sometimes that's enough to lock up the UI thread with updates. I like to send things to the UI in batches of a few dozen, or try to "throttle" them to 4Hz. We'll get back to that. It's time to bring up an old topic.
How do I make a reasonable progress indicator?
Making progress bars makes you appreciate how hard it is, and why they're usually so "dishonest". Counting files first will take a LONG time. So you'll just kind of have to cheat.
What I did the last time was count folders instead of files. I kept track of the folders so my 2nd pass through the files would be a little quicker and not a wasted effort. It also helped me reject folders I couldn't access so my file scanning was less error-prone. This did mean the progress bar was a little uneven: folders with more files took longer to process, so the bar wasn't very smooth. I don't know of a fast way to count the files in a directory, so I dealt with it.
These days, I might just set the progress bar to be animating and have a label that tells the user how many files I've counted so far. It's a pain in the butt to pretend I know how close to finished I am when I'm not.
I really, really want to make an example of this, but I'm really, really haunted by a deadline looming over my head. See if this doesn't clear up how to use BackgroundWorker, and give JMC's examples a look. And remember: you can't even make a TreeViewItem on a worker thread. Don't try.
This answer is wrong. You should be using TableAdapter and Dictionaries instead.
-
Oct 30th, 2015, 04:58 PM
#4
Re: Populate Tree View w/ Progress Bar (Background Worker? Multithreading?)
I feel a little happier now knowing a little cheating is some times needed. Because that is what i did. Although i find progress bars as annoying as webpages with music or splash screens.
you can't even make a TreeViewItem on a worker thread. Don't try.
Is there not always a but? With out me stating any fact or getting shouted at i really do remember seeing some un safe way. I could be completely wrong. Would not be the first time id never advise it though.
I do also believe you can use the BufferedGraphics in a secondary thread and draw into it's buffer and render that.
Tags for this Thread
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
|