Back
College was a great time for me. I loved having no curfew, hanging out with my friends whenever I felt like it, playing pranks on people, and going to class when I felt like it. Times were good, man, times were good.
Unfortunately for my parents, they didn't hear from me often enough. I'd get caught up in this new independence and forget to touch base with the folks that sent me to college. Sure, I'd visit every once in a while with a basket full of dirty laundry in tow. And maybe I'd make a phone call to say "hi," but that wasn't often enough for my family back home.
Windows Forms programming is a lot like that, too. You can spawn a child thread, tell it to go do something (like study) and you expect to hear back from it every once in a while. Is that too much to ask? Can't I just get a little information from my child thread every week or so? Of course you can, but it's a bit tricky when you want the child thread to update the controls on the parent form.
This makes sense. If I wanted to borrow some money from mom and dad so I could pay rent--I couldn't just walk in their house and use the checkbook to write a check to myself. No, I'd have to ask permission, and then mom and dad may write me a check to cover my costs.
The same is true with Windows Forms. The child thread can't just start modifying and messing with the parent form's controls. You could run into a race condition or some other contention that would make your application unstable.
Instead, there's this cool trick to use that utilizes the Control class's InvokeRequired property. This is the official definition:
Gets a value indicating whether the caller must call an invoke method when making method calls to the control because the caller is on a different thread than the one the control was created on.
See http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired.aspx
This is how I used it in an application that kicked off a resource-intensive process from a main form. I didn't want my application to become unresponsive during the operation, so I created a child thread that did all the heavy work. I created a delegate that the child thread could call on to send updates to the parent. Then the parent would update itself with the right information.
This is what it looks when you are trying to update the values on the parent form. This is in the master form.
/// <summary>
/// Updates the progress on the main form.
/// </summary>
/// <param name="message">The message to display at the bottom of the screen.</param>
/// <param name="progress">The progress--a number from 0 to 100.</param>
private void UpdateMainProgress(string message, int progress ) {
// make sure we are running on the right thread to be
// updating this form's controls.
if (InvokeRequired == false)
{
// we are running on the right thread. Have your way with me!
statBar.Text = message;
pbMain.Value = progress;
}
else
{
// we are running on the wrong thread.
// Transfer control to the correct thread!
var mpd = new UpdateMainProgressDelegate(UpdateMainProgress);
Invoke(mpd, message, progress);
}
}
This is how I spawned the thread from the main form. The child thread is created when a button (btnGeneratePackage) is clicked.
/// <summary>
/// Generates a deploy package by calling Exporter.ExportData on a new thread.
/// </summary>
private void GeneratePackage()
{
// set up the delegate which the process running on the new thread will
// use to update this form's progress bar and message.
var updateMainProgressDelegate = new UpdateMainProgressDelegate(UpdateMainProgress);
// Instantiate the object that will be run on a new thread.
var exporterToBeRunOnNewThread = new Exporter(updateMainProgressDelegate, this.customerId);
// Represent the method that will be called on the new thread
var threadStart = new ThreadStart(exporterToBeRunOnNewThread.ExportData);
var newThread = new Thread(threadStart) { Name = ("Exporter") };
newThread.Start();
try
{
newThread.IsBackground = false;
}
catch (Exception ex)
{
LogError(string.Format("Exception thrown: {0}", ex.Message));
throw;
}
}
/// <summary>
/// Handles the Click event of the btnGeneratePackage control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private void btnGeneratePackage_Click(object sender, System.EventArgs e) {
// for the sake of this demonstration, assume that this is a valid
// customer id. In a real application you would need to check the
// data before using it.
this.CustomerId = txtCustomerId.Text;
GeneratePackage();
}
This is how the child thread calls the delegate:
/// <summary>
/// Exports the data to the appropriate folder for the current customer.
/// </summary>
public void ExportData()
{
// Do some stuff
_UpdateMainForm("Creating Data Structure", 20);
// Do some more stuff
_UpdateMainForm("Pruning Images From DB", 40);
// Do some more stuff
_UpdateMainForm("Pruning Inventory From DB", 60);
// Do some more stuff
_UpdateMainForm("Pruning Customers From DB", 80);
// Do some other things
_UpdateMainForm("Putting in date", 90);
// Finished!
_UpdateMainForm("Customer Database Created!", 100);
return;
}
Here is a complete code listing:
namespace MyNamespace
{
using System;
using System.Windows.Forms;
using System.Data;
using System.Threading;
#region Delegates And Enums
// declare the delegate that is called from within and without this class.
public delegate void UpdateMainProgressDelegate(string message, int progress);
#endregion
/// <summary>
/// Summary description for MasterMain.
/// </summary>
public class Master : System.Windows.Forms.Form
{
/// <summary>
/// Updates the progress on the main form.
/// </summary>
/// <param name="message">The message to display at the bottom of the screen.</param>
/// <param name="progress">The progress--a number from 0 to 100.</param>
private void UpdateMainProgress(string message, int progress ) {
// make sure we are running on the right thread to be
// updating this form's controls.
if (InvokeRequired == false)
{
// we are running on the right thread. Have your way with me!
statBar.Text = message;
pbMain.Value = progress;
}
else
{
// we are running on the wrong thread.
// Transfer control to the correct thread!
var mpd = new UpdateMainProgressDelegate(UpdateMainProgress);
Invoke(mpd, message, progress);
}
}
/// <summary>
/// Generates a deploy package by calling Exporter.ExportData on a new thread.
/// </summary>
private void GeneratePackage()
{
// set up the delegate which the process running on the new thread will
// use to update this form's progress bar and message.
var updateMainProgressDelegate = new UpdateMainProgressDelegate(UpdateMainProgress);
// Instantiate the object that will be run on a new thread.
var exporterToBeRunOnNewThread = new Exporter(updateMainProgressDelegate, this.customerId);
// Represent the method that will be called on the new thread
var threadStart = new ThreadStart(exporterToBeRunOnNewThread.ExportData);
var newThread = new Thread(threadStart) { Name = ("Exporter") };
newThread.Start();
try
{
newThread.IsBackground = false;
}
catch (Exception ex)
{
LogError(string.Format("Exception thrown: {0}", ex.Message));
throw;
}
}
/// <summary>
/// Handles the Click event of the btnGeneratePackage control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private void btnGeneratePackage_Click(object sender, System.EventArgs e) {
// for the sake of this demonstration, assume that this is a valid
// customer id. In a real application you would need to check the
// data before using it.
this.CustomerId = txtCustomerId.Text;
GeneratePackage();
}
}
namespace MyNamespace
{
using System;
using System.Collections;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
/// <summary>
/// This class is used to export customer data to an external file.
/// </summary>
public class Exporter
{
//--------------------------------------------------------
// Local fields
//--------------------------------------------------------
private readonly int _custId;
private readonly UpdateMainProgressDelegate _UpdateMainForm;
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="Exporter"/> class.
/// </summary>
/// <param name="mpd">The delegate we call to update the calling form.</param>
/// <param name="customerID">The customer ID.</param>
public Exporter(UpdateMainProgressDelegate mpd, int customerID)
{
_UpdateMainForm = mpd;
_custId = customerID;
}
#endregion
/// <summary>
/// Exports the data to the appropriate folder for the current customer.
/// </summary>
public void ExportData()
{
// Do some stuff
_UpdateMainForm("Creating Data Structure", 20);
// Do some more stuff
_UpdateMainForm("Pruning Images From DB", 40);
// Do some more stuff
_UpdateMainForm("Pruning Inventory From DB", 60);
// Do some more stuff
_UpdateMainForm("Pruning Customers From DB", 80);
// Do some other things
_UpdateMainForm("Putting in date", 90);
// Finished!
_UpdateMainForm("Customer Database Created!", 100);
return;
}
}
}
Last modified about 11 months ago.
| # Comment from Coder | |||
|
10 months ago. Very helpful. Thanks for presenting this on your site. I've been wondering how to do this. |
|||
|
51 |
![]() Cloudy, 37 |
| 37 | ||
| 35 |