Click here to Skip to main content
15,880,796 members
Articles / Programming Languages / Visual Basic

Progress Bar Best Practices

Rate me:
Please Sign up or sign in to vote.
3.98/5 (30 votes)
11 Mar 2008CPOL5 min read 135.4K   123   20
How to show progress notification for long operations

Proper Progress Notification

This is going to be met with great skepticism but I'm going to say it anyway… There is only one best way to show a progress window for long operations. That answer is to show a modal progress window on the UI thread while the work is done on the worker thread.

First let’s look at why the progress window must be on the UI thread: If you try to show a dialog on a worker thread, that dialog cannot have an owner from the calling thread. Try and you'll get the exception:

Cross-thread operation not valid: Control 'Form1' accessed from a thread 
other than the thread it was created on.

And showing the dialog on another thread without an owner (modal or non) makes the dialog appear non-modal and therefore allows the user to click on your main window and loose the progress window to the depths of the Z-order making it difficult for the user to know if the operation is still in progress or not.

One trick you might be tempted to try is setting the progress window’s TopMost property to force it on top. Unfortunately, this keeps it on top of all other windows and is not ideal for users trying to multi-task while they wait for your application to do its thing.

Next, let’s examine why the work ought to be done in a separate thread at all. If you don't and the application doesn't call DoEvents, the app will quickly become non-responsive as far as Windows is concerned and users will see the “Not Responding” message up in the title bar if a user tries to click on it. And we all know that users feel entitled to tell you your app “freezes” or “hangs” when they see this.

So why not use DoEvents to allow your app to respond? Not only would you need to call frequent DoEvents to give the appearance of a responding app, but DoEvents are evil. Calling DoEvents during your operation makes your code much less reusable. Imagine you have a nice little procedure that is capable of sorting numbers. In this case, you are locking down the UI and showing a progress bar so the DoEvents seems to accomplish the desired effect. But then imagine someone reuses your sort routine in between steps in a wizard. The user clicks Next, the code runs and calls DoEvents, and for whatever reason the user decides to click Next again before the routine has finished. Maybe the user simply double clicked because they're click happy. The button click event will fire AGAIN and potentially get you into all kinds of trouble because your code will run twice. Code should be expected to be reused synchronously without fear of reentry. If we all commit to this, there would be fewer bugs in our apps and our code would be much easier to reuse. This kind of detail in my opinion is simply not something the consumer of a routine should need to worry about.

Lastly, should the progress dialog be modal or modeless? You might think that it doesn't matter. Assuming you have code that needs to run after the worker thread is complete, does it matter if the code is running in some “Thread is complete” event handler or immediately after your progress window closes? In many cases, I admit it doesn't matter. But I assert that it is good practice to do the later. This is because your code is always in some way being called from an event handler. Whether it’s a button click event or some object raising an event, with the exception of Sub Main, you are always running in an event handler of some sort. And if you spawn your worker thread and return immediately, the caller of that event may run some additional code. Now because you're showing a progress bar, you are indicating to the user that this operation did not finish. So why would you return from the event as if the operation did finish? What if there are multiple observers of that event. There’s nothing stopping another module from adding a second event handler to that same event. And if you return immediately, that code will run before your long operation has completed. This may not be a problem, but it could.

  1. It might assume that there is NOT a modal dialog displayed in the case that it needs to show its own, OR
  2. it may be dependent on your code completing before it does its thing.

Also, the event might do its own thing afterwards assuming you are done with the operation. For example, imagine an event BeforeOpen and another AfterOpen. If you do your long operation in BeforeOpen and return immediately, AfterOpen is going to fire before you're done. And consumers of that event are probably assuming you are finished with whatever it is that you are doing. And last: what if the event caller is trapping for exceptions. Waiting for your thread to complete also allows you to trap for exceptions on your thread and bubble them back up to the event issuer or even maybe appropriately set an EventArgs.Cancel flag. So far all these reasons, I assert that it is good practice to show your progress dialogs as modal unless you have some compelling reason not to.

So, how do we do all this? While there is more than one way, the easiest in my opinion is to use the System.ComponentModel.BackgroundWorker object. Here’s how:

VB.NET
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)_
                                                                  Handles Button1.Click
    BackgroundWorker = New System.ComponentModel.BackgroundWorker
    BackgroundWorker.RunWorkerAsync()
    FormProgress = New FormProgress
    FormProgress.ShowDialog()
End Sub

Private Sub BackgroundWorker_DoWork(ByVal sender As Object, _
       ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker.DoWork
    ' do your long operation here
End Sub

Private Sub BackgroundWorker_RunWorkerCompleted(ByVal sender As Object, _
        ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
        Handles BackgroundWorker.RunWorkerCompleted
    FormProgress.Close()
End Sub

Voila! It’s as simple as that. Presumably you'd also report a percent finished back through the BackgroundWorker object and its associated ProgressChanged event but perhaps you'd just have a little repeating animation on your progress form instead in which case this is all you would need.

One caveat: users can still enter Alt+F4 or click the red X in the upper right corner of your progress dialog if it’s there so you'll need to prevent this. An easy way to get around this is to just cancel the Form Close unless it’s coming from our code.

VB.NET
Private AllowClose As Boolean = False

Private Sub FormProgress_FormClosing(ByVal sender As Object, _
            ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
    If e.CloseReason = CloseReason.UserClosing And AllowClose Then
        e.Cancel = True
    End If
End Sub

Public Sub ForceClose()
    AllowClose = True
    Me.Close()
    AllowClose = False
End Sub

Just call FormProgress.ForceClose instead from your BackgroundWorker’s RunWorkerCompleted event and you're in business.

History

  • 11th March, 2008: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
I am a software architect and engineer who has been working in the industry since 1994. Although I officially started writing software many years earlier at age 11 by authoring games, graphics demos, or whatever I found interesting. I even landed my first consulting job at age 16 implementing a newly invented mathimatical algorithm for a team of researchers at the local University.

I've designed and implemented hundreds if not thousands of applications: client, web, and hybrid of all scales and was a contributing author to the Black Belt programming column of Visual Basic Programmer's Journal.

My goals when coding are always to write clean, reusable and optimized code using the latest and technology, tools and techniques.

Comments and Discussions

 
GeneralMy vote of 5 Pin
pistol3503-Jun-13 23:19
pistol3503-Jun-13 23:19 
GeneralThank You Pin
Vasanth.S.R2-Jun-10 22:26
Vasanth.S.R2-Jun-10 22:26 
QuestionHow to use progress bar & Background worker when downloading file from hard drive using C#? Pin
rasingh127-Feb-09 2:07
rasingh127-Feb-09 2:07 
QuestionWhalla? Pin
TimDJones29-Apr-08 9:16
TimDJones29-Apr-08 9:16 
GeneralAbout Best Article Pin
Izzet Kerem Kusmezer28-Apr-08 21:09
Izzet Kerem Kusmezer28-Apr-08 21:09 
GeneralRe: About Best Article Pin
98z2829-Apr-08 19:58
98z2829-Apr-08 19:58 
GeneralStatus dialog box... Pin
Member 42300745-Apr-08 16:24
Member 42300745-Apr-08 16:24 
GeneralYou are right!!!!! Pin
EricF23-Mar-08 12:12
EricF23-Mar-08 12:12 
GeneralCmon Pin
Tim Yen20-Mar-08 16:23
Tim Yen20-Mar-08 16:23 
GeneralRe: Cmon Pin
Tim Greenfield22-Mar-08 15:21
Tim Greenfield22-Mar-08 15:21 
GeneralRe: Cmon Pin
ssm62978-Oct-08 13:26
ssm62978-Oct-08 13:26 
GeneralRe: Cmon Pin
ssm62978-Oct-08 13:29
ssm62978-Oct-08 13:29 
NewsCross-thread not a problem Pin
JoseMenendez12-Mar-08 4:52
JoseMenendez12-Mar-08 4:52 
Hi Tim!
I think that the guidelines you place here are good. Even though, you can solve the first problem on the article (getting the form as an owner) by calling the following function from inside of the thread code:

VB
Public Delegate Function GetHandleCallBack(ByVal f As Form) As IntPtr

Public Function GetHandle(ByVal f As Form) As IntPtr
    
    If f.InvokeRequired Then

        Dim fn As GetHandleCallBack = New GetHandleCallBack(AddressOf GetHandle)
        Return DirectCast(f.Invoke([fn], New Object() { f }), IntPtr)

    End If

    Return f.Handle

End Function


In fact, you can interact with the whole UI by using this model of function.

Hope it helps!

Jm

GeneralRe: Cross-thread not a problem Pin
Kavan Shaban13-Mar-08 4:54
Kavan Shaban13-Mar-08 4:54 
GeneralRe: Cross-thread not a problem Pin
MaxGuernsey17-Mar-08 18:29
MaxGuernsey17-Mar-08 18:29 
GeneralRe: Cross-thread not a problem Pin
Kavan Shaban17-Mar-08 18:31
Kavan Shaban17-Mar-08 18:31 
GeneralRe: Cross-thread not a problem Pin
MaxGuernsey17-Mar-08 18:34
MaxGuernsey17-Mar-08 18:34 
GeneralRe: Cross-thread not a problem Pin
Kavan Shaban17-Mar-08 18:40
Kavan Shaban17-Mar-08 18:40 
GeneralRe: Cross-thread not a problem Pin
supercat924-May-08 12:38
supercat924-May-08 12:38 
GeneralRe: Cross-thread not a problem Pin
Fiwel18-Mar-08 6:31
Fiwel18-Mar-08 6:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.