Results 1 to 4 of 4

Thread: Populate Tree View w/ Progress Bar (Background Worker? Multithreading?)

  1. #1

    Thread Starter
    Member
    Join Date
    Jul 2010
    Posts
    35

    Question 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

  2. #2
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    111,221

    Re: Populate Tree View w/ Progress Bar (Background Worker? Multithreading?)

    Quote Originally Posted by iProRyan View Post
    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.
    Why is my data not saved to my database? | MSDN Data Walkthroughs
    VBForums Database Development FAQ
    My CodeBank Submissions: VB | C#
    My Blog: Data Among Multiple Forms (3 parts)
    Beginner Tutorials: VB | C# | SQL

  3. #3
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    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.

  4. #4
    Bad man! ident's Avatar
    Join Date
    Mar 2009
    Location
    Cambridge
    Posts
    5,401

    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.
    My Github - 1d3nt

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
  •  



Click Here to Expand Forum to Full Width