Skip to content

Doing Progress Right

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:

Thread1.Start

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.