The ProgressBar control and loops go hand-in-hand. If there is a ProgressBar on a Window, you can bet a For loop is close by. But there are right and wrong ways to do this, and most of the time I see progress code, I see the wrong way. Wrong code looks something like the following, inside the action event of a PushButton:
For i As Integer = 0 To 100 ProgressBar1.Value = i ProgressBar1.Refresh Next
But this is very, very bad code.
Why is it so bad?
In short, your app locks up entirely while the loop is doing its work. The user interface only updates once all your code is finished, so buttons will not be clickable, windows will not be movable, menus not be usable, etc. The OS will probably even list your app as “not responding” if your code takes more than a few seconds to complete. So your user experience suffers.
It’s also slower. The Refresh call demands the ProgressBar be redrawn before the next line of code is triggered. This is wasted effort as the ProgressBar doesn’t need to update so frequently.
Experienced users may have heard all this before. But there are also more subtle reasons this code is awful. On Windows, ProgressBars animate from their previous value to the new value. Since you’re locking up your app while processing however, the ProgressBar can never animate, so it will always appear to be one “step” behind.
And in Web projects, the situation is even worse. WebControls have no Refresh method. All user actions, such as a push of a button, get a single “payload” of data to send back to the browser. If we included such a method, the first call to Refresh would work, but then we use up our one payload, and all following UI updates will never reach the browser.
Doing it right
The answer is the Thread class along with the new UserInterfaceUpdate event and AddUserInterfaceUpdate method. To oversimplify, Threads allow you to execute code that does not get in the way of the UI update code. However, you absolutely must not interact with any controls from within the Thread’s Run event or anything called by the Run event. This poses an obvious problem for updating a ProgressBar. That problem is solved by using the Thread class’s UserInterfaceUpdate event which is allowed to access the UI.
Create a property on your Window called Progress As Integer. Add a Thread to the Window and it the Thread1.Run event, put the following code:
For i As Integer = 0 To 100 App.SleepCurrentThread(100) // delay to make it look like it's working Self.Progress = i Me.AddUserInterfaceUpdate Next
In this sample code, we’re not doing any actual work, but yours will of course. Rather than attempting to update the ProgressBar directly, we update the Progress property which will be used later in the UserInterfaceUpdate event. For more advanced data you can create a dictionary and pass it into the AddUserInterfaceUpdate method. Speaking of that event, here is the code to update the ProgressBar:
ProgressBar1.Value = Self.Progress
That’s it. Add a button and in its Action event start the thread:
Run the project and you’ll see the ProgressBar will update nicely, your app will be a “good citizen” and your users will be happier.
*This is an update of an older post by Thom McGrath.