Performing long running operations in Windows Forms on another thread

by Scosby Monday, August 16, 2010

This post will introduce developers to the BackgroundWorker class. Often, people ask how to perform an asynchronous operation and update their User Interface (UI) with some kind of progress information. The purpose of this post is to provide an overview of using the BackgroundWorker class to accomplish this common task. You can download the full class file at the end of this post.

While you could use a timer to perform an asynchronous operation, I feel the BackgroundWorker class is the better class to use for most peoples’ needs in a Windows Forms application. Let’s write a simple application that meets the following requirements:

  • Processes a long running operation on another thread asynchronously
  • Passes an argument to the operation to provide additional information
  • Restricts the user to running only one operation at a time
  • Updates the UI on the operation’s progress
  • Allows the user to cancel the operation

Create a new Windows Forms Application and design a form to look like the following:
Windows Forms Application Example

We have created a simple form with a textbox at the very top which will display the progress of our operation. The user can run or cancel the operation by clicking the appropriate buttons. Finally, the Options group box contains some additional information we can pass to our operation: whether to throw an exception, a user defined argument, and how many “records” we will process during the operation. Be sure to drag a BackgroundWorker control onto the form from the Components section of your toolbox.

Let’s look at the code you will need to write in order to meet the requirements of our simple application. First, we already know the BackgroundWorker can process a long running operation, so we can rely on that class to meet the first requirement. Let's construct our BackgroundWorker in the form's Load event. Add code similar to the following, note that you could also perform these actions in the designer:

private void BGWorker_Load(object sender, EventArgs e)

{

    this.backgroundWorker1 = new BackgroundWorker();

 

    //Set properties

    this.backgroundWorker1.WorkerSupportsCancellation = true;

    this.backgroundWorker1.WorkerReportsProgress = true;

 

    //Register event handlers

    this.backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);

    this.backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);

    this.backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);

}

 

Next, we need to pass an argument to the asynch operation. The RunWorkerAsync method has an overload with a parameter of type object. Thus, we can encapsulate all of our “options” into a new class and pass this into our operation. Create a nested, private class named Options in your form’s code behind to represent our group box on the form:

private class Options

{

    public bool ThrowException { get; set; }

    public string Arguments { get; set; }

    public int RecordCount { get; set; }

}

 

Next, we need to begin processing the operation.  Add a click event handler to your “Run” button similar to the following code: 

private void button1_Click(object sender, EventArgs e)

{

    if (!this.backgroundWorker1.IsBusy)

    {

        //Encapsulate our state information into a new class and pass this as an argument to the BackgroundWorker

        Options options = new Options();

        options.Arguments = this.textBoxArgs.Text;

        options.ThrowException = this.checkBox1.Checked;

        options.RecordCount = int.Parse(this.textBoxRecordCnt.Text);

 

        this.backgroundWorker1.RunWorkerAsync(options);

    }

}

In the button's click event handler, we are meeting the requirement to only allow one operation to run at a time. This is accomplished by checking to make sure the BackgroundWorker is not busy via the IsBusy property. If it were busy, than another operation is already running. You could display this information to the user in a message box if you wish by adding an else block. Otherwise, we create an instance of Options and set the properties appropriately. In the code aboe, the RecordCount property does not check for a valid int before parsing. This could cause an error and you should use better validation in production code.

 

Next, let's look at the DoWork event handler that is responsible for processing our operation on another thread. The main goal here is to retrieve our Options class, determine the configuration, and process the "records" reporting our progress back to the UI. Add code similar to the following:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)

{

    //Cast the argument to our Options class

    Options options = (Options)e.Argument;

 

    int total = options.RecordCount;

 

    for (int i = 0; i < total; i++)

    {

        //Check if the user cancelled the operation at the beginning of every iteration.

        if (this.backgroundWorker1.CancellationPending)

        {

            e.Cancel = true; //User wants us to quit processing and return.

        }

        else

        {

            Thread.Sleep(100); //simulate processing

 

            int currentRecord = i + 1; //offset zero based loop for progress reporting

 

            string message = "Records processed: " + currentRecord.ToString();

 

            // Get progress percentage

            // Note: Force decimal division else the result is rounded to the nearest integer before we can convert it to a percentage.

            decimal progress = (currentRecord / (decimal)total) * 100;

 

            //Raise the event to report progress, the UI thread will handle this in backgroundWorker1_ProgressChanged

            this.backgroundWorker1.ReportProgress((int)progress, message);

 

            if (options.ThrowException)

            {

                //This exception will be supressed at runtime and be exposed in the RunWorkerCompletedEventArgs.Error property.

                throw new InvalidOperationException("You checked the box to throw an error.");

            }

        }

    }

 

    e.Result = "I was processed on another thread. Your arguments: " + options.Arguments;

}

The most important piece of the DoWork event handler is retrieving our Options class from e.Argument. This allows us to determine what our operation should be doing. After determining our record count, we ensure the user has not clicked the "cancel" button. If the user cancelled, we must set e.Cancel to true so we know the user explicitly cancelled. Otherwise, we begin processing the operation. This consists of us simulating a call to a long running operation by calling Thread.Sleep. The other important discussion point is our requirement of informing the UI after we have processed each record. This is accomplish by the call to backgroundWorker.ReportProgress. This method allows us to report back a percentage complete and an object to represent state. In our case, we just send back a string but you could easily use the technique discussed above for passing in a custom class similar to our Options class. This technique would allow you to handle more complex scenarios than our example demonstrates.

 

Next, let's handle the ProgressChanged event. The UI responds to this event raised during the async operation, this allows you to update your UI without having to invoke a method from a non-UI thread. Add code similar to the following:

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)

{

    this.progressBar1.Value = e.ProgressPercentage;

 

    this.textBoxProgress.Text = e.UserState.ToString();

}

The ProgressChanged event handler simply takes our progress percentage and updates a progress bar on the form and displays the message we sent back in the UserState parameter of the ReportProgress method. This event will be raised each time you call ReportProgress. This is the real beauty of the BackgroundWorker. This eventing pattern makes it very simple for you to have a robust async operation that you can handle in a flexible way.

 

Next, we need to handle the RunWorkerCompleted event. This event always occurs and you need to handle it for 3 reasons:

  1. Determine if the operation threw an exception
  2. Determine if the user cancelled the operation
  3. Determine if the operation completed successfully

How you decide to handle each of these scenarios is equally important as handling the RunWorkerCompleted event itself. I will leave it up to you to determine what is appropriate, but you should start with the following code:

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)

{

    if (e.Error != null)

    {

        var newEx = new InvalidOperationException("An error occured during processing.", e.Error);

 

        MessageBox.Show(newEx.ToString());

    }

    else if (e.Cancelled)

    {

        MessageBox.Show("User cancelled operation!");

    }

    else

    {

        this.textBoxProgress.Text = e.Result.ToString();

    }

}

 

Our final requirement is to allow the user to cancel the operation. As we already covered above, this will still raise the RunWorkerCompleted event. By adding a button, the user can simply click it to cancel the operation. Only one thing happens in this method, we inform the background worker that a cancellation is pending. Our DoWork event handler is already checking for the CancellationPending to be true, and this method is what sets that property to true. Add code similar to the following to handle your cancel button's click event:

private void button2_Click(object sender, EventArgs e)

{

    this.backgroundWorker1.CancelAsync();

}

 

This post has given you an overview of how to use the BackgroundWorker class. Review the MSDN documentation for additional information on the BackgroundWorker class.

 

You can download the entire class file for this example here: BGWorker Class.zip (1.46 kb)

Tags: , , ,

IT | Programming

Validation in Windows Forms

by Scosby Thursday, February 11, 2010

Validating user input should belong in every developer’s tool kit. Today we will look at how Windows Forms applications should accomplish this task.

The main pattern for validation is event driven. The two events we will use are Control.Validating and Control.Validated. To begin, let’s create the simple WinForms application shown in Figure 1.

Figure 1 – a simple application

Let’s add one more component to spice up the UI. Open your toolbox and add an ErrorProvider control onto the form as shown in Figure 2. The ErrorProvider component will display a nice icon and tooltip next to the control, if it fails validation.

Figure 2 - adding the ErrorProvider

Let’s assume when we click our save button that we wish to validate the controls and display an icon if there is a problem. First, add the following code to the Form’s constructor after the InitializeComponent method:

        this.AutoValidate = System.Windows.Forms.AutoValidate.Disable; 

This is a handy trick to prevent implicit validation of our controls when they lose focus. You do not have to disable the Form’s AutoValidate property. However, I prefer to call it explicitly and handle all validation at once since you most likely will take action only if the entire control’s children pass validation.

Next, we need to wire up the save button’s click event. Here is the code for that event handler:

        private void buttonSave_Click(object sender, EventArgs e)       
       
{
            if (this.ValidateChildren(ValidationConstraints.Enabled))
            {
                MessageBox.Show("All controls are valid!");
                 //Logic to save...
            }
            else
            {
                MessageBox.Show("There are invalid controls on the form.");
                 //Return user to form...
            }
        }

Notice how we simply call the Form.ValidateChildren method telling it to only validate enabled controls with the ValidationConstraints.Enabled enum? The ValidateChildren method will raise the validating events for all child controls in the container and this is known as explicit validation.

Next, let’s create the Validating and Validated event handlers for both of our textboxes. The first textbox will be the Number. Here are the two events:

        private void textBoxNumber_Validating(object sender, CancelEventArgs e)
        {
            bool cancel = false;
            int number = -1;
            if (int.TryParse(this.textBoxNumber.Text, out number))
            {
                if (number > 0 && number < 11)
                {
                    //This control passes validation.
                    cancel = false;
                }
                else
                {
                    //This control has failed validation: number is not in valid range
                    cancel = true;
                    this.errorProvider1.SetError(this.textBoxNumber, "You must provide a number between 1 and 10!");
                }
            }
            else
            {
                //This control has failed validation: text box is not a number
                cancel = true;
                this.errorProvider1.SetError(this.textBoxNumber, "You must provide a valid number!");
            }
             e.Cancel = cancel;
        }

        private void textBoxNumber_Validated(object sender, EventArgs e)
        {
            //Control has validated, clear any error message.
            this.errorProvider1.SetError(this.textBoxNumber, string.Empty);
        } 


Let’s address the Validating event first. The most important thing to recognize here is how precise you can be with the validation error message in the error provider. There are 2 code paths for validation failure, and in each case we provide the user with an appropriate error message. The error provider simply takes a control and a message in the SetError method and does the rest of the work for you! If the user enters invalid data they’ll see an icon shown in Figure 3.

Figure 3 - error provider icons

After the validating event returns without being cancelled, meaning we have valid user input, then the validated event will be raised. In this case, we simply clear any error messages in the error provider.

The Name textbox works very similarly but we don’t have quite as elaborate validation logic. The validated event works the same, only pass it the name textbox instead. Here is just the validating event handler’s code:

        private void textBoxName_Validating(object sender, CancelEventArgs e)
        {
            bool cancel = false;
            if (string.IsNullOrEmpty(this.textBoxName.Text))
            {
                //This control fails validation: Name cannot be empty.
                cancel = true;
                this.errorProvider1.SetError(this.textBoxName, "You must provide your name!");
            }
             e.Cancel = cancel;

                 }


The event driven validation model in WinForms is powerful and flexible enough to allow you to create high quality apps that implement robust and maintainable validation code in your forms. Now practice your newly learned skills on your own and experiment with the error provider control!


You can read more about
user input validation in Windows Forms at MSDN.

Tags: ,

Programming