Progress Bars and the BackgroundWorker with C# and WPF

PUBLISHED ON NOV 27, 2015 — .NET, THREADING, WPF

This is just a quick introduction on my implementation of using a progress bar in WPF and C#.

The first step is to create the progress bar itself, which is a built in WPF control. So create a dialog and place a progress bar control as so:

<ProgressBar x:Name="progressBar" Margin="20,0,20,20" Height="30" Minimum="0" Maximum="100"/>

One thing to note here is that, in my opinion, for the tasks this implementation was originally designed for, an indeterminate progress bar would have been best. However I was tasked with implementing a progress bar which updated towards 100% so that users were aware of (roughly) how long was remaining on the process.

First, I modified the constructor of the dialog to accept two strings as a parameter, this sets the title on the dialog of the progress bar, and also sets a message on the dialog to inform the user of what’s happening (we’ll add a way to update this later). A private integer is added that will be used to update the progress bar incrementally if desired, and this value is just set to 5 by default.

int _percentageIncrement;
public ProgressDialog(string title, string message)
{
    InitializeComponent();
    // Set the title of the dialog
    lblTitle.Content = title;
    // Set the message on the dialog
    lblContent.Text = message;
    // Set Percentage Increment to 5 by default
    _percentageIncrement = 5;
}

Now in the .cs file for the progress bar we’ll need some methods to update the progress bar when necessary. For my project, I added three methods here.

One to update the progress which takes in a string and an integer as parameters. The string updates a label I placed above the progress bar to inform the user of what’s currently happening, while the integer sets the new value which moves the progress bar along:

public void UpdateProgress(int newProgress, string newMessage)
{
    progressBar.Dispatcher.BeginInvoke(DispatcherPriority.Normal, 
            new DispatcherOperationCallback(delegate 
    { 
        progressBar.Value = newProgress;
        lblProgressMessage.Text = newMessage;
        return null; 
    }), null);
}

The next method was to increment progress. This was added to provide a simpler way to update the progress bar at specified points in the process, giving a smoother motion to the updating of the progress bar:

public void IncrementProgress()
{
    progressBar.Dispatcher.BeginInvoke(DispatcherPriority.Normal, 
            new DispatcherOperationCallback(delegate
    {
        progressBar.Value = progressBar.Value + _percentageIncrement;
        return null;
    }), null);
}

And finally I added a method simply to set the increment amount so that it wouldn’t continuously need to be passed as a parameter throughout the process:

public void SetIncrementAmount(double newIncrement)
{
    progressBar.Dispatcher.BeginInvoke(DispatcherPriority.Normal, 
            new DispatcherOperationCallback(delegate
    {
        _percentageIncrement = newIncrement;
        return null;
    }), null);
}

On these methods I’ve made the updates to the dialog through the use of a dispatcher. Because the methods that will be updating the progress bar are called from a BackgroundWorker, this is required to stop exceptions being raised by attempting to modify the dialog off of the main UI thread.

The next thing needed is to add a class for handling the BackgroundWorker so that methods can be run whilst updating the progress bar.

To start with I created the constructor and some private variables:

private BackgroundWorker _worker;
private Action _doWorkMethod;
private ProgressDialog _progressDialog;
private string _progressBarTitle;

public ProgressWorker(Action doWorkMethod, string progressBarTitle, string progressBarMessage)
{
    // Set the Action so it can be used by the DoWork event
    _doWorkMethod = doWorkMethod;
    _progressBarTitle = progressBarTitle;
    // Create the BackgroundWorker and set the event handlers
    _worker = new BackgroundWorker();
    _worker.DoWork += _worker_DoWork;
    _worker.RunWorkerCompleted += _worker_RunWorkerCompleted;
    // Create a Progress Dialog so that it can be displayed and updated
    _progressDialog = new ProgressDialog(progressBarTitle, progressBarMessage);
    _progressDialog.progressBar.Minimum = 0;
    _progressDialog.progressBar.Maximum = 100;
    // Set the worker running
    _worker.RunWorkerAsync();
    // Display the dialog
    _progressDialog.ShowDialog();
}

The main thing to note here is the Action that is passed in as a parameter. This is done so that you can have a single entry point that allows you to open and run the progress dialog, regardless of what method you’re wanting to use - provided they take the Progress Dialog as a parameter.

This constructor registers the created BackgroundWorker to the DoWork and RunWorkerCompleted events, initializes the progress dialog, starts the worker running asynchronously, and then displays the progress dialog to the user.

The RunWorkerCompleted event handler has been kept simple, here I just use the event to close the progress dialog as it’s no longer required, however you can add whatever you see fit in here:

void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // Thread has completed so we want to close the progress bar now
    _progressDialog.Dispatcher.BeginInvoke(_progressDialog.Close, null);
}

And finally we create the DoWork event handler:

void _worker_DoWork(object sender, DoWorkEventArgs e)
{
    try
    {
        // Call the method that was passed in to the constructor
        // Also have to pass the dialog as a parameter to this method 
        // - this is done so that a method on the dialog can be called
        // to invoke an update on the value of the progress bar 
        // to show the percentage increasing
        _doWorkMethod(_progressDialog);
    }
    catch (Exception ex)
    {
        // Exception handling outside of the thread may not catch exceptions thrown 
        // in the DoWork method
        // So some exception handling should also be placed here if necessary
    }
}

This event handler simply calls the Action that was passed in to the constructor earlier, passing in the progress dialog instance so that the method can update the progress. The Action is called inside a try-catch block as errors that are thrown on the BackgroundWorker may not be handled appropriately by exception handling elsewhere, so it would be wise to do so here.

And that’s it really!

An example of opening the dialog and running a method is like so:

ProgressWorker worker = new ProgressWorker(MyDoWorkMethod, "Progress bar title", 
	"Progress bar message");

Where MyDoWorkMethod is any method that accepts a ProgressDialog as a parameter.

As mentioned earlier, some parts of this implementation are less than ideal - primarily around the way the progress bar is updated for me. This could be avoided by using an indeterminate progress bar, as this isn’t being used for something we can accurately timebox such as a download or a loop performing a specific number of actions.

Another way would be not to pass the Action as a parameter and call logic directly in the DoWork event handler, this way you can simply use the BackgroundWorkers ReportProgress method to update the dialog.

Any suggestions or criticisms, feel free to comment or contact me directly! Always keen to learn and improve 🙂

comments powered by Disqus