Click here to Skip to main content
Click here to Skip to main content

ProgressForm: A simple form linked to a BackgroundWorker

By , 1 Mar 2011
 

Introduction

This is my first article on CodeProject, I hope you will find it useful.

When I develop software, I often need to ask the user to wait for a long operation to finish, and usually allow him/her to cancel it. Whatever I do in that operation (it can be downloading a file, saving a big file to disk, etc.), I always need the same things:

  • I want the user to wait for the operation to finish through a modal dialog box
  • I want the user to see its progress
  • I want the user to be able to cancel it

Since I couldn't find a "ready to use" form for this purpose on the web (maybe I didn't search well?), I decided to write my own.

ProgressForm.JPG

Background

The BackgroundWorker class contains everything we need to achieve this. We just need to provide a dialog box around it.

Using the code

ProgressForm already contains a BackgroundWorker, all you need is to provide a method to do the work.

ProgressForm form = new ProgressForm();
form.DoWork += new ProgressForm.DoWorkEventHandler(form_DoWork);
//if you want to provide an argument to your background worker
form.Argument = something;

To start the BackgroundWorker, just call ShowDialog. The return value will depend on how the worker finished:

DialogResult result = form.ShowDialog();
if (result == DialogResult.Cancel)
{
     //the user clicked cancel
}
else if (result == DialogResult.Abort)
{
     //an unhandled exception occured in user function
     //you may get the exception information:
     MessageBox.Show(form.Result.Error.Message);
}
else if (result == DialogResult.OK)
{
     //the background worker finished normally
     //the result of the background worker is stored in form.Result
}

Finally, the worker method will look like this:

void form_DoWork(ProgressForm sender, DoWorkEventArgs e)
{
    //get the provided argument as usual
    object myArgument = e.Argument;
 
    //do something long...
    for (int i = 0; i < 100; i++)
    {
        //notify progress to the form
        sender.SetProgress(i, "Step " + i.ToString() + " / 100...");
        
        //...
        
        //check if the user clicked cancel
        if (sender.CancellationPending)
        {
            e.Cancel = true;
            return;
        }
    }
}

If you want to change only the progress bar or only the progress text, SetProgress has several overloads:

public void SetProgress(string status);
public void SetProgress(int percent);
public void SetProgress(int percent, string status);

And the last customizable things: there are two predefined strings: CancellingText and DefaultStatusText. CancellingText is the text that will be displayed if the user clicks Cancel.

How it works

ProgressForm just embeds a BackgroundWorker and wraps its main functionalities.

First, I designed a form as shown on the picture. Then I added the BackgroundWorker with the main event handlers:

public partial class ProgressForm : Form
{
    public ProgressForm()
    {
         InitializeComponent();
 
         worker = new BackgroundWorker();
         worker.WorkerReportsProgress = true;
         worker.WorkerSupportsCancellation = true;
         worker.DoWork += new System.ComponentModel.DoWorkEventHandler(worker_DoWork);
         worker.ProgressChanged += new ProgressChangedEventHandler(
             worker_ProgressChanged);
         worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
             worker_RunWorkerCompleted);
    }
    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
    }
    void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
    }
    void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
    }
    BackgroundWorker worker;
}

Now we must expose to the user the DoWork event. I added a new delegate so we can easily access the form members:

public delegate void DoWorkEventHandler(ProgressForm sender, DoWorkEventArgs e);
public event DoWorkEventHandler DoWork;

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    //the background worker started
    //let's call the user's event handler
    if (DoWork != null)
        DoWork(this, e);
}

OK, we have our worker and the event for the user. Now we want the worker to start as soon as the form is displayed. Let's do this in the Load event:

void ProgressForm_Load(object sender, EventArgs e)
{
    worker.RunWorkerAsync();
}

Now, let's write a method to notify the progress, and add code into our ProgressChanged event handler:

public void SetProgress(int percent, string status)
{
    worker.ReportProgress(percent, status);
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    if (e.ProgressPercentage >= progressBar.Minimum &&
        e.ProgressPercentage <= progressBar.Maximum)
    {
        progressBar.Value = e.ProgressPercentage;
    }
    if (e.UserState != null)
        labelStatus.Text = e.UserState.ToString();
}

We are almost there. Now we just need to handle the Cancel button:

void buttonCancel_Click(object sender, EventArgs e)
{
    //notify the worker we want to cancel
    worker.CancelAsync();
    //disable the cancel button and change the status text
    buttonCancel.Enabled = false;
    labelStatus.Text = "Cancelling..."
}

One last thing: we want to close the form automatically once the worker finishes, and since our worker will be started through the ShowDialog method, it would be nice if it could directly return the result:

void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    //ShowDialog return value will inform whether the worker finished properly or not
    if (e.Error != null)
        DialogResult = DialogResult.Abort;
    else if (e.Cancelled)
        DialogResult = DialogResult.Cancel;
    else
        DialogResult = DialogResult.OK;
    //close the form
    Close();
}

The main job is finished! I added a few more stuff like predefined strings for the status, preventing the status to be changed if a cancel is pending, or passing an argument to the worker function.

You will find here the complete source code for that class:

/// <summary>
/// Simple progress form.
/// </summary>
public partial class ProgressForm : Form
{
    /// <summary>
    /// Gets the progress bar so it is possible to customize it
    /// before displaying the form.
    /// Do not use it directly from the background worker function!
    /// </summary>
    public ProgressBar ProgressBar { get { return progressBar; } }
    /// <summary>
    /// Will be passed to the background worker.
    /// </summary>
    public object Argument { get; set; }
    /// <summary>
    /// Background worker's result.
    /// You may also check ShowDialog return value
    /// to know how the background worker finished.
    /// </summary>

    public RunWorkerCompletedEventArgs Result { get; private set; }
    /// <summary>
    /// True if the user clicked the Cancel button
    /// and the background worker is still running.
    /// </summary>
    public bool CancellationPending
    {
        get { return worker.CancellationPending; }
    }

    /// <summary>
    /// Text displayed once the Cancel button is clicked.
    /// </summary>
    public string CancellingText { get; set; }
    /// <summary>
    /// Default status text.
    /// </summary>
    public string DefaultStatusText { get; set; }
    /// <summary>
    /// Delegate for the DoWork event.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">Contains the event data.</param>
    public delegate void DoWorkEventHandler(ProgressForm sender, DoWorkEventArgs e);
    /// <summary>
    /// Occurs when the background worker starts.
    /// </summary>
    public event DoWorkEventHandler DoWork;

    /// <summary>
    /// Constructor.
    /// </summary>
    public ProgressForm()
    {
        InitializeComponent();

        DefaultStatusText = "Please wait...";
        CancellingText = "Cancelling operation...";

        worker = new BackgroundWorker();
        worker.WorkerReportsProgress = true;
        worker.WorkerSupportsCancellation = true;
        worker.DoWork += new System.ComponentModel.DoWorkEventHandler(worker_DoWork);
        worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
        worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
            worker_RunWorkerCompleted);
    }

    /// <summary>
    /// Changes the status text only.
    /// </summary>
    /// <param name="status">New status text.</param>
    public void SetProgress(string status)
    {
        //do not update the text if it didn't change
        //or if a cancellation request is pending
        if (status != lastStatus && !worker.CancellationPending)
        {
            lastStatus = status;
            worker.ReportProgress(progressBar.Minimum - 1, status);
        }
    }
    /// <summary>
    /// Changes the progress bar value only.
    /// </summary>
    /// <param name="percent">New value for the progress bar.</param>
    public void SetProgress(int percent)
    {
        //do not update the progress bar if the value didn't change
        if (percent != lastPercent)
        {
            lastPercent = percent;
            worker.ReportProgress(percent);
        }
    }
    /// <summary>
    /// Changes both progress bar value and status text.
    /// </summary>
    /// <param name="percent">New value for the progress bar.</param>
    /// <param name="status">New status text.</param>
    public void SetProgress(int percent, string status)
    {
        //update the form is at least one of the values need to be updated
       if (percent != lastPercent || (status != lastStatus && !worker.CancellationPending))
       {
           lastPercent = percent;
           lastStatus = status;
           worker.ReportProgress(percent, status);
       }
    }

    private void ProgressForm_Load(object sender, EventArgs e)
    {
        //reset to defaults just in case the user wants to reuse the form
        Result = null;
        buttonCancel.Enabled = true;
        progressBar.Value = progressBar.Minimum;
        labelStatus.Text = DefaultStatusText;
        lastStatus = DefaultStatusText;
        lastPercent = progressBar.Minimum;
        //start the background worker as soon as the form is loaded
        worker.RunWorkerAsync(Argument);
   }

    private void buttonCancel_Click(object sender, EventArgs e)
    {
        //notify the background worker we want to cancel
        worker.CancelAsync();
        //disable the cancel button and change the status text
        buttonCancel.Enabled = false;
        labelStatus.Text = CancellingText;
    }

    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        //the background worker started
        //let's call the user's event handler
        if (DoWork != null)
            DoWork(this, e);
    }

    void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        //make sure the new value is valid for the progress bar and update it
        if (e.ProgressPercentage >= progressBar.Minimum &&
            e.ProgressPercentage <= progressBar.Maximum)
        {
            progressBar.Value = e.ProgressPercentage;
        }
        //do not update the text if a cancellation request is pending
        if (e.UserState != null && !worker.CancellationPending)
            labelStatus.Text = e.UserState.ToString();
    }

    void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        //the background worker completed
        //keep the resul and close the form
        Result = e;
        if (e.Error != null)
            DialogResult = DialogResult.Abort;
        else if (e.Cancelled)
            DialogResult = DialogResult.Cancel;
        else
            DialogResult = DialogResult.OK;
        Close();
    }

    BackgroundWorker worker;
    int lastPercent;
    string lastStatus;
}

Conclusion

This form is quite simple but since I use it often, I thought it would be useful to some of you.

History

  • Revision 2: Added the "How it works" section.
  • Revision 3: Uploaded SampleApplication.zip once more.
  • Revision 4: Added the ProgressBar property and changed the SetProgress functions so that they call ReportProgress only if needed.

License

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

About the Author

Olivier Levrey
France France
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Questionform not visiblememberstijn.vandenbroucke9 Mar '13 - 8:02 
AnswerRe: form not visiblememberOlivier Levrey10 Mar '13 - 8:40 
GeneralMy vote of 5memberRafael Queiroz6 Feb '13 - 1:20 
GeneralMy vote of 5membermjbohn11 Apr '11 - 21:36 
GeneralMy vote of 5memberEspen Harlinn16 Mar '11 - 10:56 
GeneralRe: My vote of 5memberOlivier Levrey16 Mar '11 - 23:02 
GeneralMy vote of 5mvpthatraja4 Mar '11 - 5:22 
GeneralRe: My vote of 5memberOlivier Levrey16 Mar '11 - 23:02 
GeneralMy vote of 5memberYangyong Qin1 Mar '11 - 4:02 
GeneralJust sayin'memberPIEBALDconsult1 Mar '11 - 3:52 
GeneralRe: Just sayin'memberOlivier Levrey1 Mar '11 - 4:00 
General5'd and excellentmvpLuc Pattyn28 Feb '11 - 13:30 
GeneralRe: 5'd and excellentmemberOlivier Levrey28 Feb '11 - 21:55 
AnswerRe: 5'd and excellentmvpLuc Pattyn28 Feb '11 - 22:13 
GeneralRe: 5'd and excellentmemberOlivier Levrey1 Mar '11 - 3:21 
Generalsample applicationmembercasper.kinsun21 Feb '11 - 14:21 
GeneralRe: sample applicationmemberOlivier Levrey21 Feb '11 - 21:48 
GeneralMy vote of 3memberSledgeHammer0121 Feb '11 - 7:25 
GeneralRe: My vote of 3memberOlivier Levrey21 Feb '11 - 8:29 
GeneralRe: My vote of 3memberalhambra-eidos1 Mar '11 - 3:05 
GeneralRe: My vote of 3memberOlivier Levrey1 Mar '11 - 3:10 
GeneralRe: My vote of 3memberalhambra-eidos1 Mar '11 - 3:17 
GeneralRe: My vote of 3memberEd Bouras2 Mar '11 - 2:11 

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 1 Mar 2011
Article Copyright 2011 by Olivier Levrey
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid