Introduction
This article proposes a solution for updating Windows Form components when using cross-thread method calls.
Since .NET Framework 2.0, you can't make an explicit method call to a Windows Form component method (say, you wanted to alter the text of a label). This happens because Windows Forms is not thread-safe (in other words, it can't guarantee that the behavior using threads will be the same as using only the main program thread).
What to do then, when your beautiful, multi-core optimized code has an user interface that needs to be updated from whatever thread is needed? You have to use the BeginInvoke
method, so you can make a thread-safe call.
The article covers basically three areas:
- Creating and handling custom events
- Cross-thread method call
BackgroundWorker
class
Note: English is not my mother language, so the text may not be accurate sometimes.
Background
To understand this code, I recommend that you have at least some contact with the C# language (the more, the better).
If you have already worked with event handling before, this tutorial may improve your skills.
If you never had the chance or patience to learn how to implement a custom event or how to use it with multiple threads, this is specially for you.
Using the Code
The code is fully documented. But for those who are in a hurry and seek just some pointers and advice, I'll give a brief explanation.
The code consists basically of two classes:
- Door.cs
- Form1.cs
The Door
class has the delegates and events declaration. I have opted to include a BackgroundWorker
to insert some delay between changing the Door
state from closed to open and vice-versa.
The Form1
class is a Windows Form with two buttons (Open door and Close door) and a label to display the current status. The door starts up closed, so you have to click Open
to raise two events.
The first event raised is the event telling that the door is opening. The second one is raised when the door is open (when the background worker finishes its job).
By design, you can't close a door that isn't open and can't open a door that isn't closed (the in-between events can't be altered even if you click the other button).
Now some code fun.
The following code is inside the Door
class:
public delegate void OpeningDoor ();
public event OpeningDoor RaiseDoorOpening;
public delegate void OpenDoor ();
public event OpenDoor RaiseDoorOpened;
public delegate void ClosingDoor ();
public event ClosingDoor RaiseDoorClosing;
public delegate void ClosedDoor ();
public event ClosedDoor RaiseDoorClosed;
And now the Open
method for the Door
class:
public void Open ()
{
if ( !_isOpen )
{
RaiseDoorOpening ();
#region Open door delay
BackgroundWorker delayer = new BackgroundWorker ();
delayer.DoWork += new DoWorkEventHandler ( OpenDelayer_DoDelay );
delayer.RunWorkerAsync ();
#endregion
}
else
return;
}
private void OpenDelayer_DoDelay ( object sender, DoWorkEventArgs e )
{
Thread.Sleep ( 3000 );
_isOpen = true;
RaiseDoorOpened ();
}
As for the Form1
class, the relevant parts are:
The constructor:
public Form1 ()
{
InitializeComponent ();
door = new Door ();
door.RaiseDoorOpening += new Door.OpeningDoor(door_OnDoorOpening);
door.RaiseDoorOpened += new Door.OpenDoor ( door_OnDoorOpen );
door.RaiseDoorClosing += new Door.ClosingDoor(door_OnDoorClosing);
door.RaiseDoorClosed += new Door.ClosedDoor ( door_OnDoorClosed );
}
Now the event handlers for opening the door:
private void door_OnDoorOpening ()
{
string msg = "Opening the door...";
Console.WriteLine ( msg );
object [] p = GetInvokerParameters ( msg, Color.Orange );
BeginInvoke ( new UpdateDoorStatusLabel ( UpdateLabelText ), p );
}
private void door_OnDoorOpen ()
{
string msg = "Door opened.";
Console.WriteLine ( msg );
object [] p = GetInvokerParameters ( msg, Color.Green );
BeginInvoke ( new UpdateDoorStatusLabel ( UpdateLabelText ), p );
}
Helper method for creating the required parameters for the delegate:
private object [] GetInvokerParameters (string labelMessage,
Color labelColor )
{
object [] delegateParameter = new object [ 2 ];
delegateParameter [ 0 ] = labelMessage;
delegateParameter [ 1 ] = labelColor;
return delegateParameter;
}
Helper method to update the text and change the forecolor
:
private void UpdateLabelText ( string text, Color color )
{
lblDoorStatus.Text = text;
lblDoorStatus.ForeColor = color;
}
The Open
button Click event handler:
private void btnOpenDoor_Click ( object sender, EventArgs e )
{
door.Open ();
}
Points of Interest
This code helped me to remember how to use delegates with C# and how to handle events. I also learned how to use the BackgroundWorker
for the delay to work properly and the cross-thread call was a bonus (I didn't expect to use it at first when I was coding the event handling).
History
- 2008-02-09 - 1.0 - First release
Covers events for Opening
, Open
, Closing
, Close
in the Door
class.