Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Progress Bars, Threads, Windows Forms, and You

4.94/5 (72 votes)
18 Dec 2012CPOL23 min read 327.3K   19.5K  
Using asynchronous progress bars on your form from start to finish

Introduction

Has your program ever locked up while it was performing a lengthy operation? Have you ever wondered how to implement a nice jazzy progress bar to prove to your end user that the program hasn't crashed? Have you experimented in the past with ProgressBars and found yourself unbelievably frustrated trying to get them to work properly?

If you answered "yes" to any of the questions above, don't worry - you are not alone. Anyone who has worked with Visual X.NET has pulled their hair out in rage trying to get some piece of their program to work properly at some time in their programming career. This article will help address the concerns about one of those pieces - the progress bar.

This article assumes a basic understanding about how Windows Forms are created in Visual Studio, specifically how to create a form, put a button on the form, and attach an event to that button. It also assumes an understanding of C# and fairly strong understanding of basic programming and object-oriented programming concepts and principles. 

A Note About Progress Bars

Before you go about the process of incorporating a progress bar into your program, ask yourself first if your program even needs it. Progress bars, like other more advanced technologies such as Regular Expressions and LINQ, are incredibly powerful and useful for both the programmer and the end user when used properly, but become cumbersome and annoying when heavily abused.

Here are some situations when a progress bar would be appropriate:

  • A utility that copies a large number of files from one location to another.
  • A math program that calculates the nth digit of PI.
  • A web browser that loads large web pages.
  • A reporting program that gets information from a number of databases when generating reports.
  • A conversion software that encodes and saves video information.

When correctly implemented, a progress bar is a visual tool of reassurance that tells the end user that your program hasn't locked up or frozen. This is especially helpful when running tasks that can potentially take hours to complete. Without some kind of progress indication, there is no way to tell if the program has broken or is simply taking an unusually long time.

There are some practices using progress bars that should be avoided, as they often lead to bulky code and frustrated end users. Here's a short list of those practices:

  • When running a process that is known to not take longer than a second or two to complete, simply running the process in a background thread could be more productive than a progress bar.
  • When running multiple consecutive processes as part of one action, use one bar for the entire action than a new one for each process. Attaching multiple consecutive progress bars to a single action frustrates the end user in that they don't know how many progress bars there will be before the entire action is done. (However, displaying one progress bar for progress on the current process and another for the entire action is perfectly fine.)
  • Try to update the value of the progress bar based on the actual progress of the action as much as possible rather than assigning arbitrary values to each stage of the process. This will give it a tangible sense of progress.
  • Do not set up a progress bar that has only two steps, zero percent and completed. If for some reason you are in a position where you cannot measure the progress of your action while it is running, use the Indeterminate state.
  • When running a process that has many increments (in the magnitude of tens of thousands or greater, for example), do not update the progress bar for each of those increments. Only update the progress bar when it would make a noticeable visible difference to do so. An increment of one on a scale of a hundred thousand will not prompt a change of the length of the progress meter. 

Getting Started

Let's go ahead and get the ball rolling, shall we? For starters, we will need a program to run our time-consuming process on. Create a form and stick a button in the center of it. 

 

Nothing too fancy. In order to simulate some processing, go ahead and a Thread.Sleep call to the button's Click event.  

C#
private void button1_Click(object sender, EventArgs e)
{ 
    Thread.Sleep(5000); 
    MessageBox.Show("Thread completed!");
}

The very model of complexity, I tell you what. Go ahead and run the program and click the button.

After clicking the button, you'll notice that the entire form becomes unresponsive while the main thread is processing - you can't even exit out of the window! (Of course, once the thread stops sleeping, everything you've done to the window suddenly happens all at once, meaning if you clicked the red X while the form was unresponsive, the program will close once it's done sleeping.)

The GUI Thread

What is happening here is this - there is basically one thread that handles just about everything in a form, from initialization and monitoring controls to reacting to button clicks and updating the interface. This simplifies the entire process in that nothing crucial to the form needs to worry about multithreading. However, this also has the glaring drawback of whenever this main thread locks up for any reason, everything comes to a screeching halt until the thread becomes available again.

Think of it like a busy interstate highway. For some reason, the Traffic Division decided to make this highway a single lane with no intersections or shoulders. Sure, since there is nowhere for passing cars to conflict with each other by crossing lanes or turning out of or into traffic, that single lane runs at maximum efficiency (assuming that all of the cars are driving at exactly the speed limit). However, if anything happens to disrupt traffic, like an accident or road maintenance, all the cars have to stop and wait for the problem to clear up before moving again.

At this point, some of you are probably wondering, "Well, why don't those idiotic road engineers give that highway another lane?" And to this, I say you are absolutely right. The highway needs another lane so traffic can run smoothly even when one of the lanes locks up, just like this program needs another thread to do all the heavy lifting so the main thread can still do its job when a heavy process is running elsewhere.

Multithreading

Ah, that great monstrosity of programming everywhere. Multithreading is the act of making two processes in a program run simultaneously independent of each other. Many programmers use multithreading to great effect, but it is also a double-edged sword. If you aren't careful about your code, multithreading can quickly become confusing, cumbersome, and downright impossible to debug.

Properly managed, however, multithreading is the asynchronous programmer's best friend. It enables the program to do a great many things that would be overly complex or downright impossible to do using only a single thread. In this case, it allows us to run a process without holding up all the important things that the main thread is working on, such as, you know, looking pretty.

Let's change the code in our button's Click event method to take advantage of this whole multithreading concept (don't forget to add using System.Threading to the top of your class):

C#
private void button1_Click(object sender, EventArgs e)
{
    Thread backgroundThread = new Thread(
        new ThreadStart(() =>
        {
            Thread.Sleep(5000);
            MessageBox.Show("Thread completed!");
        }
    ));
    backgroundThread.Start();
}

If there are terms in that code you don't recognize, don't worry. I'll step through each line and explain it to you.

C#
Thread backgroundThread = new Thread 

The Thread class is, appropriately, the core class for threading. Every multithreaded application is going to use the Thread class somewhere. An instance of the Thread class (in this case, the object "backgroundThread") represents a thread that can be started, stopped, and monitored through use of that object.

For more information regarding the Thread class, check out the MSDN page on the topic: http://msdn.microsoft.com/en-us/library/system.threading.thread.aspx.

C#
new ThreadStart(() =>
{
    Thread.Sleep(5000);
    MessageBox.Show("Thread completed!");
}

The constructor of the Thread class takes a special kind of delegate as a parameter called a ThreadStart. The ThreadStart class (and its close relative, the ParameterizedThreadStart) represents the actual code the thread will be running, shown within the curly braces. When the thread gets to the end of the code, it automatically terminates the thread.

(Note: If you don't know what the "() =>" syntax is, that is a bit beyond the scope of this article. Suffice it to say that it is called a lambda expression, it is part of the LINQ structure, and it is basically shorthand for making anonymous methods.)

If you're curious to know more about the ThreadStart class, here's its MSDN page: http://msdn.microsoft.com/en-us/library/system.threading.threadstart.aspx.

C#
backgroundThread.Start();

This is the line of code that actually gets the thread running. Don't forget to put this code in your program, or you will sit and wonder why your process isn't running, even after you pressed that button.

If you run the code now, you will notice that you can press the button as many times as you like, and doing so doesn't cause the form to lock up anymore. However, nothing really happens until after the thread stops sleeping and pops up the dialog box. This means that, during that five seconds while the thread is working, there is no indication whatsoever that something is happening!

That just will not do. The person using your program needs to know when something they did started an important process. Otherwise they may wonder if the program is broken and click the button again, starting yet another thread. (Some of you may have discovered this if you mashed the button in your form then found your screen getting zerg-rushed by dialog boxes five seconds later.) We need something to tell the user that what they did started something important.

Progress Bars in Practice 

Here we finally are - the part where we actually talk about using progress bars! I'm sure I could put in several more paragraphs talking about how useful progress bars are, but you probably already know that. Otherwise you wouldn't even be here wanting to learn how to use them. So without further ado, let's get into it.

First, let's add a ProgressBar to the bottom of our form, just under the button.

Next, let's change our button's Click event method a bit so that it can calculate progress, then send this progress to our shiny new progress bar:

C#
private void button1_Click(object sender, EventArgs e)
{
    Thread backgroundThread = new Thread(
        new ThreadStart(() =>
        {
            for (int n = 0; n < 100; n++ )
            {
                Thread.Sleep(50);
                progressBar1.Value = n;
            }

            MessageBox.Show("Thread completed!");
            progressBar1.Value = 0;
        }
    ));
    backgroundThread.Start();
}

You probably have noticed that, instead of a single call to Thread.Sleep, we instead have a for loop that calls Thread.Sleep 100 times. This is so that we have a process that takes roughly the same amount of time as that single call, but now we have information that we can feed to our progress bar. Then, once the for loop finishes, we want to reset the progress bar's value to 0 to show that the process is done. 

The way we actually give that information to the progress bar is simple and painless - setting the property Value. As long as the value you pass it is between the minimum and maximum values (more on those later), the progress bar will update itself as far as calculating how much of the bar is filled. In this case, we are feeding it every value from 0 to 99, and you can watch that progress by just running the program and pressing the button. 

Hold on now... The program is getting angry at us. If you run this code and press the button, it will spit this error out at you:

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

Remember that whole spiel I gave earlier about the program being managed only by a single thread? Well it turns out that the all-knowing and benevolent engineers at Microsoft decided to take it a step further and explicitly disallow other threads from meddling with its tidy little assortment of controls. To us programmers, what this means is that you are forbidden from setting property values of any control from any thread other than the one it was created on.

Since every control in our form was created from a single thread, the main thread, we can't set the value of our progress bar from our new thread that we are running. There is still hope, however, so don't panic yet. (There will be plenty of time to do that later.)

Invoke and BeginInvoke

Because Microsoft isn't completely devoted towards making our jobs as difficult as possible, they have granted us access to a few tools of saving grace - the Invoke methods, namely Invoke and BeginInvoke. The purpose of these methods is to allow programmers to perform these multithreaded operations without having to reverse-engineer the whole updating function the main thread works on in the background.

In a nutshell, the Invoke methods take a delegate and add it to a queue in the main thread. Then, the next time the main thread is idle (i.e. not working on any of our code) it will perform the action in that delegate. In this way, you can trick the progress bar into thinking that our code is running on the main thread, even though it originated from somewhere else entirely. Silly progress bar.

The difference between Invoke and BeginInvoke is that Invoke is a synchronous operation, meaning that once you call this method, it will wait for the delegate to be processed in the main thread before continuing, whereas BeginInvoke is an asynchronous operation, so it will hand the delegate over to the main method then continue on its merry way without waiting for said delegate to complete. Both of these methods have their uses and disadvantages, so it becomes an exercise for the programmer to determine which is more appropriate for what the program is doing. In general, you want to use BeginInvoke unless you have a reason for wanting to wait for the delegate to be process, such as the delegate updating a crucial bit of information that the rest of your thread depends on.

In this case, let's go ahead and use BeginInvoke, since there isn't really any reason to hang up our important and time-consuming process to wait for the progress bar to update itself. Update the code in the button's Click event method to use this method:

C#
private void button1_Click(object sender, EventArgs e)
{
    Thread backgroundThread = new Thread(
    new ThreadStart(() =>
    {
        for (int n = 0; n < 100; n++ )
        {
            Thread.Sleep(50);
            progressBar1.BeginInvoke(
                new Action(() =>
                    {
                        progressBar1.Value = n;
                    }
            ));
        }

        MessageBox.Show("Thread completed!");
        progressBar1.BeginInvoke(
            new Action(() =>
                {
                    progressBar1.Value = 0;
                }
        ));
    }
    ));
    backgroundThread.Start();
}

And that's all she wrote about using BeginInvoke. If you run the program now and click the button, you should see the progress bar slowly fill up to max, then display a dialog box once it's finished. 

(If you're paying attention, you might have noticed the other addition to the code - the Action class. This is because the Invoke and BeginInvoke methods only take delegates, so simply using a lambda expression won't cut it. The Action class wraps anonymous methods in a delegate, allowing their use in cases such as this. The more you know, and such...) 

A Problem With Multithreading 

If you're one of those people who likes seeing how far you can push a program before it breaks (or you're one of the people who got Taliban-ed by dialog boxes earlier), you may have noticed that, if you mash the button, the progress bar starts doing some weird dance moves that you might have seen before at a strobe-light convention.

This is because when you press the button multiple times, you are starting many simultaneous threads that do the whole count-to-a-hundred thing. Besides causing your processor to want to commit seppuku, all these threads are contesting for the progress bar's attention every step of the way, making the actual progress indicator in the bar jump sporadically from position to position. What is happening is that all the threads add their own progress to the queue in the main thread in no particular order. The progress bar then sorts through each one, but only retains whichever was the last one in the queue and updates its graphic accordingly. 

The simplest and most appropriate solution would be to only allow the button to be pressed if it hasn't been already. After all, you don't want the user to be able to run a process if they just barely started that same process. That opens all sorts of potential bugs and glitches of the sort that the best way to debug them would be to design your program to not get them in the first place. 

Fortunately, implementing such a feature is as simple as adding a boolean variable and a single if check:

C#
private bool isProcessRunning = false;
private void button1_Click(object sender, EventArgs e)
{
    if (isProcessRunning)
    {
        MessageBox.Show("A process is already running.");
        return;
    }

    Thread backgroundThread = new Thread(
        new ThreadStart(() =>
        {
            isProcessRunning = true;

            for (int n = 0; n < 100; n++ )
            {
                Thread.Sleep(50);
                progressBar1.BeginInvoke(
                    new Action(() =>
                        {
                            progressBar1.Value = n;
                        }
                ));
            }

            MessageBox.Show("Thread completed!");
            progressBar1.BeginInvoke(
                    new Action(() =>
                    {
                        progressBar1.Value = 0;
                    }
            ));

            isProcessRunning = false;
        }
    ));
    backgroundThread.Start();
}

And presto! If you run the program now and hit the button, it carries on as usual. Now I dare you to press that button again. Just try it. Didn't work, huh? Your program is now too clever for you to break it so easily.

Now that your program is free of all those pesky concurrent threads stepping on each other's shoes, let's move on to another possible implementation of the progress bar. 

Progress Bars In Dialog Boxes 

All this talk of progress bars within the form is nice for background processes, but what if you have a task that requires the user to not touch the form while it runs? Like, maybe you have a button that saves the current state of your program. It would be a problem if the user was to change that state while the saving process was running.

This is where progress dialogs come in. They provide a handy progress indicator while also disabling any input to the main form while it is open.

To make one, just create a new form called ProgressDialog and slap on a ProgressBar

You can go ahead and set a bunch of dialog-relevant properties if you want (such as StartPosition and FormBorderStyle), but we'll call this good for all intents and purposes.

Let's add a new button to our main form as well, so we have a place where we can launch this dialog. 

Before, when we wanted to update our progress, we could just call it directly since the code was within the same class as the progress bar. Now, however, the progress bar will be within a separate form and is private by default, which means your thread code won't have the access needed to properly modify the progress bar's value.

Now, before you say we can just change the modifier for the progress bar so that it is public, I would  say that, yes, we could do that. We could also build a bridge across the Grand Canyon entirely out of straw and paper-mache, or we could get to the moon if we built a slingshot big enough. While you can make controls have public access, it is generally bad practice to do so. Making a control public exposes all of its properties, some of which don't work properly if you change them from other classes or forms. It is better practice to either use properties to expose only what you want to be able to access or, in our case, create a method to do the accessing for you.

In the code for the ProgressDialog, create a new public method called UpdateProgress that takes an integer as a parameter. Within that method, do all the calls to BeginInvoke and such that we did before. You should end up with something similar to this:

C#
public void UpdateProgress(int progress)
{
    progressBar1.BeginInvoke(
        new Action(() =>
            {
                progressBar1.Value = progress;
            }
    ));
}

This method will handle the actual Invoking that our whole multithreaded goal requires. Now our button's Click event method just needs to worry about doing its super important processing. We will go ahead and copy-paste over the code from earlier with a few choice differences: 

C#
private void button2_Click(object sender, EventArgs e)
{
    if (isProcessRunning)
    {
        MessageBox.Show("A process is already running.");
        return;
    }

    ProgressDialog progressDialog = new ProgressDialog();

    Thread backgroundThread = new Thread(
        new ThreadStart(() =>
        {
            isProcessRunning = true;

            for (int n = 0; n < 100; n++)
            {
                Thread.Sleep(50);
                progressDialog.UpdateProgress(n); // Update progress in progressDialog
            }

            MessageBox.Show("Thread completed!");
            // No need to reset the progress since we are closing the dialog
            progressDialog.BeginInvoke(new Action(() => progressDialog.Close()));

            isProcessRunning = false;
        }
    ));
    backgroundThread.Start();

    progressDialog.ShowDialog();
}

As you can see, the code is for the most part unchanged. The key differences are, obviously, related to the addition of the ProgressDialog we just created. 

C#
ProgressDialog progressDialog = new ProgressDialog();
...
progressDialog.ShowDialog();

These bits of the code create and display the ProgressDialog. Using the ShowDialog method rather than the Show method will pause the main thread while the dialog is open, preventing the user from making any changes while your process does its work. 

C#
progressDialog.UpdateProgress(i); 

This is a call to the method we made earlier. Since the UpdateProgress method includes a call to Invoke within it, it isn't necessary to use one here. 

C#
progressDialog.BeginInvoke(new Action(() => progressDialog.Close())); 

This line will close the dialog when the process is done running. Note that, since we created the dialog on a different thread and telling a form to close itself is a pretty significant change, you need to call Invoke in order to use it. 

And that's it! Run the program and press the new button. It should pop up the dialog, complete with working and filling progress bar, and the best part is that, while the process is running, any interaction with the main form isn't allowed.

Before we call this article wrapped up, there's one more thing we should go over. 

Indeterminate Mode  

While developing an application with a progress bar, you should always design your code so it is easy to quantify how "complete" your processes are. However, sometimes there just isn't any way to tell how close to finishing a particular process is. Some examples of this are:

  • When the application loads a number of files and the exact number is unknown. 
  • When the application receives a variable amount of data from another application. 
  • When the application is working with an external library with limited access to the source code. 

In these cases, you can't really give a definite amount of progress since there is nothing that indicates when the process will be done. However, you still need some indication of progress to show your users that something is, in fact, being done. This is where indeterminate mode steps in.

Indeterminate mode is a setting on the progress bar that shows that the program is doing something, but it isn't known how long that something will take. Visually, the progress bar will display a continuously scrolling marquee that will never stop. (That is, until you either change the progress bar or close the form.)

To set a progress bar to indeterminate mode is simple. All you need to do is set the progress bar's style to Marquee:

C#
progressBar.Style = ProgressBarStyle.Marquee;

Conversely, to set the progress bar back to its normal progress-scrolling ways, set its style to Blocks:

C#
progressBar.Style = ProgressBarStyle.Blocks; 

(Note: The progress bar has a third possible style, Continuous. However, it is treated the same as Blocks unless the program is running under a Windows XP or earlier environment, or if visual styles are disabled. These topics are beyond the scope of this article, but suffice it to say that you should never have to use Continuous unless you have a specific reason for doing so.) 

To show this capability, let's add a new method to our ProgressDialog called SetIndeterminate which takes a boolean value as a parameter:

C#
public void SetIndeterminate(bool isIndeterminate)
{
    progressBar1.BeginInvoke(
        new Action(() =>
        {
            if (isIndeterminate)
            {
                progressBar1.Style = ProgressBarStyle.Marquee;
            }
            else
            {
                progressBar1.Style = ProgressBarStyle.Blocks;
            }
        }
    ));
}

Again, since this method is intended to be called from a separate thread, we must use Invoke for it to work properly. Otherwise, this method is fairly self explanatory - if you pass it a true value, the dialog sets its progress bar's style to Marquee, otherwise it sets its value to Blocks

In order to showcase this functionality, let's add one more button to our main form.

In the Click event method of the new button, add this code:

C#
private void button3_Click(object sender, EventArgs e)
{
    if (isProcessRunning)
    {
        MessageBox.Show("A process is already running.");
        return;
    }

    ProgressDialog progressDialog = new ProgressDialog();
    progressDialog.SetIndeterminate(true);

    Thread backgroundThread = new Thread(
        new ThreadStart(() =>
        {
            isProcessRunning = true;

            Thread.Sleep(5000);

            MessageBox.Show("Thread completed!");

            progressDialog.BeginInvoke(new Action(() => progressDialog.Close()));

            isProcessRunning = false;
        }
    ));
    backgroundThread.Start();

    progressDialog.ShowDialog();
}

This code should look a bit familiar. In order to simulate a process that has no quantifiable way to determine how close it is to being done, we have done away with our fancy for loop in favor of a single call to Thread.Sleep. The other addition is the call to ProgressDialog's SetIndeterminate method, which we just created. 

If you run the program now, you should be able to click the third button and have our newly indeterminate ProgressDialog appear for five seconds. Bask in its glory, if you wish.

Handling InvalidOperationExceptions 

Yes, I promised before that indeterminate mode was the last thing. However, there is one more thing I need to tell you before I let you go.

One caveat about using the Invoke methods is that they only work while a form has been loaded, is visible, the whole shebang. If you try to call these methods on a form that either hasn't yet been displayed or has been closed, you get an exception that looks an awful lot like this:

C#
InvalidOperationException was unhandled:
Invoke or BeginInvoke cannot be called on a control until the window handle has been created.

The reason for this is the Invoke method is designed to allow you to inject processes into the form or control's main thread, and it's problematic if that thread isn't currently taking requests because its busy not running. The good news, however, is that during these times that Invoke can't be used, it isn't actually needed. You can do all sorts of property setting and manipulating on any left field thread. Once that form is displayed, however, all bets are off.

That's all well and good, but how can you tell if a form is currently being displayed? Sure, you can check its Visible property or subscribe to its Load event method, but there's a more reliable way. All forms and controls that have an Invoke method also have an InvokeRequired property, which returns a boolean value that tells you (surprise!) whether you need to use an Invoke method to so all that special property manipulation magic. 

Change the methods in your ProgressDialog class to use this newly found gem of information:

C#
public void UpdateProgress(int progress)
{
    if (progressBar1.InvokeRequired)
    {
        progressBar1.BeginInvoke(
            new Action(() =>
                {
                    progressBar1.Value = progress;
                }
        ));
    }
    else
    {
        progressBar1.Value = progress;
    }
}

public void SetIndeterminate(bool isIndeterminate)
{
    if (progressBar1.InvokeRequired)
    {
        progressBar1.BeginInvoke(
            new Action(() =>
            {
                if (isIndeterminate)
                {
                    progressBar1.Style = ProgressBarStyle.Marquee;
                }
                else
                {
                    progressBar1.Style = ProgressBarStyle.Blocks;
                }
            }
        ));
    }
    else
    {
        if (isIndeterminate)
        {
            progressBar1.Style = ProgressBarStyle.Marquee;
        }
        else
        {
            progressBar1.Style = ProgressBarStyle.Blocks;
        }
    }
}

And there you have it. On either of these methods, if the form or control currently requires an Invoke method, then it uses an Invoke method, and if not, it doesn't use one. Doesn't get more straightforward than that.  

There are a few other places in the program where you have to use InvokeRequired as well. In the Click event method for the first button in the main form, there are two calls to BeginInvoke on the progress bar. Both of these calls are potential error spots if, for any reason, the form closes while that thread is running. In both of those cases, adding the check to InvokeRequired will relieve the potential error in this process. 

C#
if (progressBar1.InvokeRequired)
    progressBar1.BeginInvoke(new Action(() => progressBar1.Value = n));
...
if (progressBar1.InvokeRequired)
    progressBar1.BeginInvoke(new Action(() => progressBar1.Value = 0));

Finally, in the Click event methods for the other two buttons, there are calls to the ProgressDialog's Close method. These calls are also points of vulnerability for a couple of reasons. For one, calling Close while InvokeRequired is true is also a no-no, but another reason to include a check at these places is to avoid redundancy - you don't want to try to close a form when it is already closed.

C#
if (progressDialog.InvokeRequired)
    progressDialog.BeginInvoke(new Action(() => progressDialog.Close()));

More Fun With Progress Bars 

There are a few more interesting things you can do with progress bars, such as:

  • This article works with the default  minimum and maximum values of the ProgressBar, which is 0 and 100, respectively. You aren't locked in to these values, however. They are easily changed via the ProgressBar's properties Minimum and Maximum
  • You can add an accompanying label to show status updates as well as progress do provide more informative feedback to the end user. 
  • Combine the Marquee and Blocks styles so you can show indeterminate mode during times you don't have the required information to calculate a progress scale, then switch to default mode once you do have that information. (For example, while you are scanning for files to import, you would set the progress bar indeterminate mode, then switch back once you have a list of files you can get a count from.)
  • If you have a good idea of how long a particular part of a process is going to take, you can use that time to calculate an ETA. Users love ETAs. (Point of interest: Users love accurate ETAs.)
  • If you have an extremely long process that can be broken down into segments, you can use two progress bars - one for each segment, and one for the process as a whole. It really helps give the user the impression that progress is being made without having to carefully watch for per-pixel increments of the progress bar. 

History 

1.0 - Release.

License

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