Click here to Skip to main content
15,867,568 members
Articles / Programming Languages / C#

Events Made Simple

Rate me:
Please Sign up or sign in to vote.
4.82/5 (25 votes)
17 Feb 2009CPOL12 min read 59.4K   578   90   15
Events made simple

Target of this Article

This article is aimed at beginners to events. More advanced users may find some of the topics interesting if they've not investigated them before, or haven't used them for a while. It is a complete rewrite of my original article, which left a lot to be desired in the way of examples and explanation.

This article is NOT an in depth study of the underlying principles and processes used in event/delegate handling. There are many other great articles and blog entries out there that cover that stuff. In fact, the inspiration for this article came from my early experiences in attempting to use and understand events, but becoming totally confused by articles of that type. Once you've finished reading this article and have grasped the concepts I explore, do go ahead and read those other articles. They will then be much clearer!

Prerequisites

You should have some C# coding experience, know how methods and properties are constructed, parameters are passed and values are returned and a knowledge of access modifiers as these will not be explained here.

What Is An Event?

An event is... well - an event! Something, somewhere has happened. Maybe a button has been clicked, a value has changed, or is about to change and we have the opportunity of cancelling it. Whenever something happens to an object, the likelihood is that there is an event that can be used. We can easily use events in our own classes to broadcast to the world when something happens. An important point is that we don't know if anybody is interested in our event(s), there could be 0 to ∞ interested parties, and we know nothing about them!

Index

Simple Class

So let's get started. For this article, I have used a normal Windows Forms application. Add a class SimpleClass and add an instance of it to Form1 so you end up with this:

C#
// SimpleClass.cs
using System;
namespace YourNamespace
{
    public class SimpleClass
    {
    }
}
C#
// Form1.cs
using System;
using System.Windows.Forms;
namespace YourNamespace
{
    public partial class Form1 : Form
    {
        private SimpleClass simpleClassInstance = new SimpleClass();
        public Form1()
        {
            InitializeComponent();
        }
    }
}

Simple Event

So how do we create an event? Easy! From 2.0 all we have to do is add this to our SimpleClass (1.1 users will need to do it 'the old fashioned way' - see Simple Event With Delegate):

C#
public event EventHandler SimpleEvent;

This creates the event, but it's never used at the moment which the compiler will warn about if you attempt to build now. The EventHandler part is the magic that allows us to communicate with many different objects that we actually have no knowledge of. For those interested, hover your mouse over it and you'll see it's actually a delegate. For now, don't worry about what a delegate is, or what it does. I'll go into a little detail when we need to later on.

Now let's create a method that actually raises this event:

C#
protected virtual void OnSimpleEvent(EventArgs e)
{
    EventHandler eh = SimpleEvent;
    if (eh != null)
        eh(this, e);
}

So what's all this other stuff? Let's go one line at a time:

C#
protected virtual void OnSimpleEvent(EventArgs e)

We're simply creating a method called OnSimpleEvent that takes a parameter of type System.EventArgs. EventArgs is the base class that is used in almost all event handling. We'll investigate this more in the next example.

C#
EventHandler eh = SimpleEvent

Our SimpleEvent that we previously created is already of the delegate type EventHandler. We're simply creating a copy of this to work with.

C#
if (eh != null)

If no one is listening for (subscribed to) our event, then it will be null, so we need to check this now to avoid a NullReferenceException.

C#
eh(this, e)

The EventHandler (that is the type of our event) needs two parameters: object sender and EventArgs e. We simply send ourselves (this) and the EventArgs we took as a parameter into this method.

We can now raise the event by calling this method, but it's protected. OnEvent methods should never be called directly from external sources. So let's create another method that will in turn call this method.

C#
public void PerformSimpleEvent()
{
    OnSimpleEvent(EventArgs.Empty);
}

Now we have a public method we can call from our form. Add a Button to Form1 and in it's Click handler (just double click it in the designer), call the simpleClassInstance's public method.

C#
private void btnSimpleEvent_Click(object sender, EventArgs e)
{
    simpleClassInstance.PerformSimpleEvent();
}

Now all we need to do is subscribe to the SimpleEvent. In the Form1 constructor, type simpleClassInstance.SimpleEvent+= and intellisense will pop up the rest for you. Press TAB twice and it will fill the line and create a method for you too. You should have something like this:

C#
using System;
using System.Windows.Forms;
namespace YourNamespace
{
    public partial class Form1 : Form
    {
        private SimpleClass simpleClassInstance = new SimpleClass();
        public Form1()
        {
            InitializeComponent();
            simpleClassInstance.SimpleEvent += 
		new EventHandler(simpleClassInstance_SimpleEvent);
        }
        void simpleClassInstance_SimpleEvent(object sender, EventArgs e)
        {
            throw new NotImplementedException();
        }
        private void btnSimpleEvent_Click(object sender, EventArgs e)
        {
            simpleClassInstance.PerformSimpleEvent();
        }
    }
}

Notice the signature of the method matches that of EventHandler! Obviously, we don't want to throw an exception when the event is raised. We'll just show a MessageBox, so replace the 'throw' line with:

C#
MessageBox.Show("SimpleEvent raised");

Now run the code and click the button.

If that's all clear, then read on. If not, go back and read it through again as you'll need to understand it for the following sections.

Passing Data with Custom EventArgs

Passing data is basically a matter of creating our own class that derives from EventArgs. Create a new class called SimpleEventArgs and paste in the following (change the namespace to the one you're using):

C#
using System;
namespace YourNamespace
{
    public class SimpleEventArgs : EventArgs
    {
        // private member variable to hold an int
        private int m_Value;
        // constructor that takes an int parameter
        public SimpleEventArgs(int value)
        {
            m_Value = value;
        }
        // public readonly property so the int can be accessed
        public int Value
        {
            get { return m_Value; }
        }
    }
}

This time our event declaration will look a little different. Add these to our SimpleClass.

C#
public event EventHandler<SimpleEventArgs> SimpleWithArgsEvent;
private int m_Value = 0;

The EventHandler delegate we used earlier has a generic counterpart EventArgs<> that allows us to specify any other class that derives from EventArgs. We've also added a field to hold an int value. This time we're going to raise the event when a property value changes:

C#
public int Value
{
    get { return m_Value; }
    set
    {
        if (m_Value != value)
        {
            m_Value = value;
            OnSimpleWithArgsEvent(new SimpleEventArgs(m_Value));
        }
    }
}

And add the OnEvent method:

C#
protected virtual void OnSimpleWithArgsEvent(SimpleEventArgs e)
{
    EventHandler<SimpleEventArgs> eh = SimpleWithArgsEvent;
    if (eh != null)
        eh(this, e);
}

Notice, all we've done is change the eh declaration so it's the same as the event's. Add another Button to Form1 and add a Click handler again. In it, increment the new property we created.

C#
private void btnSimpleWithArgsEvent_Click(object sender, EventArgs e)
{
    simpleClassInstance.Value++;
}

Back to the constructor, subscribe to the new event and auto generate the method. This time we're going to display the value in our SimpleEventArgs in a MessageBox.

C#
void simpleClassInstance_SimpleWithArgsEvent(object sender, SimpleEventArgs e)
{
    MessageBox.Show(String.Format("Value = {0}", e.Value));
}

Run the code and click the button a few times.

Cancelable Events

Cancelable events are much the same as any other with a couple of exceptions. They need to derive from CancelEventHandler plus use CancelEventArgs (which is annoyingly in the System.ComponentModel namespace so you'll need to add that to the usings in both Form1 and SimpleClass) and we need to check that CancelEventArgs.Cancel is false before allowing whatever it is that raises the event to commit. In the following example, it's a property change. To facilitate this, the possible value is saved in a variable, and the corresponding member variable is only changed after e.Cancel has been checked.

In SimpleClass:

C#
public event CancelEventHandler SimpleCancelableEvent;
private string m_Text = string.Empty;
private string newText;
public string Text
{
    get { return m_Text; }
    set
    {
        if (m_Text != value)
        {
            newText = value;
            // using new as CancelEventArgs.Empty is of 
	   // type EventArgs not CancelEventArgs
            OnSimpleCancelableEvent(new CancelEventArgs());
        }
    }
}
protected virtual void OnSimpleCancelableEvent(CancelEventArgs e)
{
    CancelEventHandler eh = SimpleCancelableEvent;
    if (eh != null)
    {
        // this line invokes the method(s) outside this class
        eh(this, e);
        // the method(s) have returned
        // e.Cancel is set by the last method called from the invocation list.
        if (!e.Cancel)
         m_Text = newText;
    }
}

And again, add a button to Form1, add the Click handler and in it change the Text property we created above.

C#
private void btnSimpleCancelableEvent_Click(object sender, EventArgs e)
{
    simpleClassInstance.Text = "Hello World!";
}

Subscribe to the new event and auto generate the method, and set e.Cancel in the new method.

C#
void simpleClassInstance_SimpleCancelableEvent(object sender, CancelEventArgs e)
{
   e.Cancel =
      !(MessageBox.Show(
      "Allow Text to change?",
      "Cancelable Event",
      MessageBoxButtons.YesNo
      , MessageBoxIcon.Question)
      == DialogResult.Yes);
}

The SimpleClass.Text property will only change if e.Cancel is false... sometimes! To understand this, we need to look into the behind the scenes stuff in a bit more detail.

Delegates (and Multicast) Oversimplified

So, what is a delegate? Good question. MSDN and many other sites start talking about them being "similar to function pointers in C++ ". Not much help if you've never used C++, but let's try and break that seemingly simple statement down.

A function in C++ is the same as a method in C#.
A pointer literally points to a location in memory.

So, a function pointer basically knows where a particular method is stored so it can invoke it by using that particular part of the RAM. That's what our EventHandler has been magically doing for us. In fact, it's been even more magical. If there was more than one subscriber to our event, it kept track of each one. It does that using an invocation list - a collection of methods that are to be invoked when the event is raised.

Why do we need to know this? Well, in the last example, all the subscribers will have been called - so which one sets the Cancel? The last one in the list, so if the first or nth one attempted to cancel, it would have been overruled by the very last method invoked.

Event Cancelable From Any Subscriber

The new event and property are much the same as before, but the OnEvent method is slightly different.

C#
public event CancelEventHandler SimpleCancelableAnyEvent;
private string m_TextAny = string.Empty;
private string newTextAny;
public string TextAny
{
    get { return m_TextAny; }
    set
    {
        if (m_TextAny != value)
        {
            newTextAny = value;
            // using new as CancelEventArgs.Empty is of 
	   // type EventArgs not CancelEventArgs
            OnSimpleCancelableAnyEvent(new CancelEventArgs());
        }
    }
}
protected virtual void OnSimpleCancelableAnyEvent(CancelEventArgs e)
{
    CancelEventHandler eh = SimpleCancelableAnyEvent;
    if (eh != null)
    {
        bool cancel = false;
        foreach (CancelEventHandler subscriber in eh.GetInvocationList())
        {
            subscriber(this, e);
            if (e.Cancel)
            {
                cancel = true;
                break;
            }
        }
        if (!cancel)
        m_TextAny = newTextAny;
    }
}

We get the invocation list from the event, and call each entry in the list via its individual delegate. If any one of them sets Cancel to true, we break out and cancel.

Cancelable Event With Args

Of course, there's nothing to stop us passing our own derivation of CancelEventArgs, all we need to do is change the various bits of code to EventHandler<SimpleCancelEventArgs> (assuming that's what we call our custom args). There's an example in the demo if you need it.

Asynchronous Events

This is most often not a problem, but all our code so far is blocked by the invoked method(s). Should a method for any reason (maybe it does a large amount of file copying for example) take a long time before it returns, then our SimpleClass instance is also blocked until the invoked method returns. This situation can be alleviated by calling each delegate in the invocation list asynchronously. This is done by calling the delegate's BeginInvoke method. BeginInvoke does exactly what it says - it begins the invocation of the method only, it doesn't wait around for it to finish but returns immediately. The beauty is, it does it on a separate thread for us, so it doesn't hold up the thread we're using for everything else. This is an example of another new event for our SimpleClass.

C#
public event EventHandler SimpleAsyncEvent;
public void PerformSimpleAsyncEvent()
{
    OnSimpleAsyncEvent(EventArgs.Empty);
}
protected virtual void OnSimpleAsyncEvent(EventArgs e)
{
    EventHandler eh = SimpleAsyncEvent;
    if (eh != null)
        foreach (EventHandler subscriber in eh.GetInvocationList())
            subscriber.BeginInvoke(
                this, e, new AsyncCallback(
                    OnAsyncCompleted), subscriber);
    // Console.WriteLine("All methods invoked!");
}
protected virtual void OnAsyncCompleted(IAsyncResult asyncResult)
{
    EventHandler subscriber = (EventHandler)asyncResult.AsyncState;
    subscriber.EndInvoke(asyncResult);
    // Console.WriteLine("Completed Asynchronously");
}

This is very similar to what we did in the previous example. Instead of calling subscriber(this, e) we call its BeginInvoke method which takes the same values plus two additional parameters, AsyncCallback and object. AsyncCallback is a delegate for a method in our class that will be called (called back) when the invoked method finally returns. This additional method must take a parameter of IAsyncResult to match the AsyncCallback delegate. We get the originally invoked method and call EndInvoke on it to release the thread.

The downside of this is: If the method invoked needs to do anything on the main thread (updating the UI for example) then you will get a cross thread operation error. We can get around this in Form1 by invoking a separate method.

C#
private void btnSimpleAsyncEvent_Click(object sender, EventArgs e)
{
    simpleClassInstance.PerformSimpleAsyncEvent();
}
void simpleClassInstance_SimpleAsyncEvent(object sender, EventArgs e)
{
    // Attempting to do anything on the UI thread here will raise
    // a System.InvalidOperationException so use BeginInvoke.
    BeginInvoke(new MethodInvoker(
        delegate() { OnSimpleAsync(sender, e); })
        );
}
void OnSimpleAsync(object sender, EventArgs e)
{
    MessageBox.Show("SimpelAsynceEvent raised");
}

Note: We called BeginInvoke here on Form1 which is a control. Control.BeginInvoke does not require a matching EndInvoke unlike delegate.BeginInvoke. There is some debate on this issue so I advise a good web search if you are concerned (have a :beer: handy because you might be there a while!).

Simple Event With Delegate

OK, so back to some more simple stuff! Now we're familiar with the idea of delegates, we can construct a new version of our first event without using EventHandler, but declaring our own delegate instead.

C#
public delegate void SimpleDelegateAndEventHandler(object sender, EventArgs e);
public event SimpleDelegateAndEventHandler SimpleDelegateAndEvent;

Here we have made the return value of the delegate void and the parameters are the usual for a basic event signature. That's all there is to it. The rest of the code will be exactly as before, except referring to our delegate instead, and the new event of course.

Different Event Signatures

There is nothing to say we always have to have the 'sender, e' signature for a method. That is the standard and I recommend you observe it, but we should examine how we can change it to suit our needs. All we need to do is to use our own delegate and the signature we desire (or none at all if no data is to be passed).

C#
public delegate void SimpleIntegerEventHandler(int value);
...
// in OnEvent method
eh(value);

When we create our subscriber, the method signature will match ...MethodName(int value){...}

Returning Values

In the same way that we changed the delegate signature above, we can change the return type to get a return value from the invoked method(s). Again, I would recommend following the standards and using/checking your custom event args, but you can do it this way if you wish.

C#
public delegate bool SimpleReturnValueEventHandler();
...
// in OnEvent method
if (eh != null)
{
    // last method invoked will take priority.
    // iterate through the invocation list if you want to act on any individual.
    bool result = eh();
    // do your thing with result here
    // if (result)

Form Communication Using Events

Many times on the CodeProject forums, people ask about passing values from one form to another. All too often people give solutions, that even though they work, are bad practice. Let's construct a common example, a typical substandard answer, and an easy solution using events.

"I have two forms, Form1 and Form2. Form1 creates Form2, which has a TextBox on it. I need to get the value of that text box into Form1. How do I do it?"

A typical answer goes something like this...
"Change the constructor of Form2 to take a Form1 parameter, have a public method/property in Form1 and pass the text to that."

C#
// NOT recommended!
private Form1 m_Form1;
public Form2(Form1 form1)
{
    InitializeComponent();
    m_Form1 = form1;
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
    // call method in Form1
    m_Form1.ReceiveText(textBox1.Text);
}

So what's wrong with that - it works doesn't it? Well, yes it does, but now Form2 is coupled to Form1. If Form2 ever needs to be used without Form1 you're stuck. Form2 is nothing to do with Form1 so it shouldn't know anything about it. What if other objects need to know about the Text - you can probably see how this way is quickly going to get really messy.

Solution (OOP practices observed):
If the TextBox's text changing may be of interest to anyone else, the form should raise an event accordingly.

C#
// Form2.cs
using System;
using System.Windows.Forms;
namespace YourNamespace
{
    public partial class Form2 : Form
    {
        public event EventHandler<UpdateTextEventArgs> UpdateText;
        public Form2()
        {
            InitializeComponent();
        }
        private void textBox1_TextChanged(object sender, EventArgs e)
        {
            OnUpdateText(new UpdateTextEventArgs(textBox1.Text));
        }
        protected virtual void OnUpdateText(UpdateTextEventArgs e)
        {
            EventHandler<UpdateTextEventArgs> eh = UpdateText;
            if (eh != null)
                eh(this, e);
        }
    }
}
C#
// UpdateTextEventArgs.cs
using System;
namespace YourNamespace
{
    public class UpdateTextEventArgs : EventArgs
    {
        private string m_Text;
        public UpdateTextEventArgs(string text)
        {
            m_Text = text;
        }
        public string Text
        {
            get { return m_Text; }
        }
    }
}
C#
// in Form1
// instantiating Form2 from button click
private void btnFormCommunication_Click(object sender, EventArgs e)
{
    Form2 form2 = new Form2();
    // subscribe
    form2.UpdateText += new EventHandler<UpdateTextEventArgs>(form2_UpdateText);
    // showing as dialog for demo
    form2.ShowDialog(this);
    // unsubscribe
    form2.UpdateText -= form2_UpdateText;
    form2.Dispose();
}
// changes form caption
void form2_UpdateText(object sender, UpdateTextEventArgs e)
{
    Text = e.Text;
}

Conclusion

I have tried to be as clear as possible and build gradually throughout this article to give a fairly decent introduction to the world of events. I hope you've found it understandable, and will find it a useful reference in future. All the sections have a corresponding working example in the demo code attached.

All comments are welcome, just post below.

History

  • 22nd January, 2008: Initial version
  • 14th February, 2009: Complete rework with demo and comprehensive examples

License

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


Written By
CEO Dave Meadowcroft
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralTo Member 950420 Pin
DaveyM694-Feb-08 14:17
professionalDaveyM694-Feb-08 14:17 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.