Events and Delegates
Events are an extremely important and fundamental part of OOP and .net. An event is when something happens - for example when you click on a button in either a WinForms application or an asp.net application then a Button Click event happens.
Code can be wired up to respond to events - for example when you click on a Search Button then some code fires which does a search - pretty simple and obvious yes? Another example is when you click on a Close button then some code runs which closes a window. Of course not all events are button clicks - thats just an easy and well known example.
Another example and one we will use in a real example is that of a clock. Lets say we have an alarm clock program. When the current time is equal to the time we set our alarm for when an event will happen - an alarm event. We can wire up code for this which will alert us to the alarm in whatever way (Messagebox, sound etc).
Ok so lets get started with our alarm clock example as a practical example will help to see how things work. Firstly create a Windows Forms app called AlarmClock.
Rename the default form to MainForm and change the caption to Alarm Clock which should then look something like this:
We will need something to show the current time - ie the actual clock so lets put this on as two labels as follows:
The first label is called CurrentTimeLabel and the second one is called CurrentTime. The CurrentTime has had its background and border changed for visual presentation.
We will now need a clock class. When the form loads we will create an instance of our clock class and it will sit there ticking away. The clock will change every second. We will actually use two events for our example. The first event will be a TimeChanged event. This will of course change every second and when it does we will have code in our main form update the clock display. We will get this working first and then write code to deal with the alarm side of things.
Ok so lets write our clock class. Initially we need the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace AlarmClock
{
public class Clock
{
private DateTime currentTime;
public Clock()
{
Thread tickClock = new Thread(new ThreadStart(TickClock));
tickClock.Start();
}
private void TickClock()
{
while (true)
{
currentTime = DateTime.Now;
Thread.Sleep(1000);
}
}
}
}
As you can see our clock class simply ticks away using threading so as not to lock up the system. It updates its internal time then the thread sleeps for 1 second and the loop continues. Hopefully this is simple enough. We now need to write code in our form to instantiate this. In keeping with the theme of events we will write this code in the Load event of the Main Form!
private void MainForm_Load(object sender, EventArgs e)
{
Clock alarmClock = new Clock();
}
We need to now introduce an event which we can raise when the time changes so that the main form knows to update its clock display. Before we do this lets look at the steps involved in using events which are as follows:
- Something happens in our code to require raising an event - ie time changes
- Our clock code raise the event
- Other code (main form) has subscribed to this event and runs a specified method when the event happens
The event is actually a kind of delegate and in our main form we do the following:
So the TimeChanged event is a delegate under the hood. We add our AlarmClock_TimeChanged method to the list of methods which this delegate references - remember a delegate can have more than one method which it references. And so when the clock class raises the event it will simply execute the delegate which will in turn execue the method or methods that it references. It is interesting to note that the use of the event keyword in the clock class is actually an access modifier and it limits client code's access to our delegate by only allowing it to add methods to the delegate invocation list using +=. If it were allowed to use = then it would overwrite any other subscribers which would be wrong. Visual Studio also sees the event keyword and presents us with a bolt of lightening in intellisense.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace AlarmClock
{
public class Clock
{
private DateTime currentTime;
public delegate void TimeChangedHandler(object o, ClockEventArgs e);
public event TimeChangedHandler TimeChanged;
private void OnTimeChanged(ClockEventArgs e)
{
if (TimeChanged != null)
{
TimeChanged(this, e);
}
}
public Clock()
{
Thread tickClock = new Thread(new ThreadStart(TickClock));
tickClock.Start();
}
private void TickClock()
{
while (true)
{
currentTime = DateTime.Now;
ClockEventArgs cArgs = new ClockEventArgs(currentTime);
OnTimeChanged(cArgs);
Thread.Sleep(1000);
}
}
}
}
So we can see that we define an event called TimeChanged. This is based on the delegate TimeChangedHandler. When we want to raise this event we do so with
ClockEventArgs cArgs = new ClockEventArgs(currentTime);
OnTimeChanged(cArgs);
It is convention for delegate signatures for events to work this way. They pass an object which is the thing raising the event and also an EventArgs instance. This is often a class which derives from System.EventArgs and is designed to provide information pertinent to the event in question. In our example we want to pass what the new time is - ie what the time has changed to so we have a ClockEventArgs class as follows:
So to recap - when time changes in our clock class instance then it raises an event with:
ClockEventArgs cArgs = new ClockEventArgs(currentTime);
OnTimeChanged(cArgs);
Our Main Form code has subscribed to this event as follows:
So that when the event happens then the AlarmClock_OnTimeChanged method will run in the Main Form:
This will then update the Clock Display in the form with the new updated value which it gets from the passed in ClockEventArgs instance (e). To see this in action try changing the Thread sleep time from 1 second to 5 seconds or 10 second.
This whole process is not an easy one to explain but hopefully you have now got the gist of it. We can now proceed to add code to get the alarm clock itself working.
We can now extend our sample application to have an alarm event.
First add two new labels to the form so that we hav something like the form below:
We can then make some changes to our clock class as follows:
- Add a private field to store the alarm time.
- Add a public property for client code access to the alarm time.
- Add an alarm event
- Add code to raise the alarm event
- Add a bool to keep track of whether the alarm has been rung - otherwise it would just keep ringing!
With all of these changes incorporated into our code then our clock class should now look like this (new bits highlighted in yellow):
We also need to make changes to our form code so that it sets the alarm time and responds to the alarm event. To keep things simple we shall just set the alarm time to 10 seconds into the future as follows:
Take special note of the line which sets the CheckForIllegalCrossThreadCalls to false. The way we are updating a control on our form here is not thread safe as other threads could attempt to update the control at the same time leaving it in an invalid state. We therefore have to set CheckForIllegalCrossThreadCalls to false as otherwise the runtime will throw an exception. This is fine for this little example as otherwise it is a nice illustration of events.
To make safe cross thread updates to windows form controls you should really use other techniques - I didn't do so here as I didn't want to make this example unnecesairily complex and detract from the main focus of the article. A good article on this can be found at the following address:
http://msdn.microsoft.com/en-us/library/ms171728.aspx
Another really good article can also be found here:
http://channel9.msdn.com/playground/Sandbox/167196/
After a little thought I have decided to address this issue and another issue with this article. Before looking at the issue of safe cross thread calls, lets first have a look at what happens if we run this applicaiton from Visual Studio and then close the form after a couple of seconds. If you wait a few more seconds then our alarm will fire!
This is becuase althought we have closed the form, we still have a Clock object which has a thread running and ticking away - it's just that we cannot see it as it has no visual representation like the form does. So what we need to do is kill off the Clock object when the form closes. To do this we will make our Clock implement the IDisposable interface as this is the standard way of doing this. We will then need to put a Dispose method in our Clock class which will kill off our thread. Our new clock class will therefore look like this - note also that the tickClock Thread object is now class wide so we can access it from the Dispose method.
So all we now need to do is make sure that when we close our form then we call the Dispose method of the Clock object. In order to be able to access the Clock object from within the Form constructor and the FormClosed method, we need to make the Clock object a class level field and so our new Form code should now be as follows and if you run this then the form will close and the Clock object's thread will also be aborted and since the Clock object will now no longer have any references to it so there is nothing else to keep the program alive and so it will end.
The next issue to address is when we try and update our TextBox in the AlarmClock_TimeChanged event handler. Now although this method is declared in our form class, when it runs it will be running as a delegate and so will actually run in the thread which the Clock object uses for ticking. Manipulating form controls from threads which did not create them is unsafe and can lead to thread related problems and this is why we had to set the CheckForIllegalCrossThreadCalls to false. Although setting this to false seems to make the program work, it is a hack and sooner or later doing this will cause you problems.
There are various ways around this but the solution I will show is by far the cleanest I could find. What we basically try and do is to get the AlarmClock_Timechanged event handler to run in the thread of the main form. We can do this using the SynchronizationContext class which is in the System.Threading namespace. This class has a static property called Current which represens the current context when ever we access this property. So what we do is pass this into our code which does the threading and then this class has a reference to the Synchronization context of the forms' main thread. We are then able to use this to raise our event in that context - ie on the main form thread.
So first we need to change our Clock class so that it has a private field of type SynchronizationContext. we then set this by passing it into the Clock constructor as follows:
We then need to change our OnTimeChanged method which will need to look like this:
Now I'm not going to pretend I understand all of this as far as I can tell we are calling the Send method of the currentSynchronization object which is the threading context of the main form. The post method takes a delegate of type SendOrPostCallBack which is an anonymous delegate to invoke our actual event delegate. Its easy enough to use this code without actually understanding how it all works and that's good enough for me!