Submitted by BillB on 8/23/2010

(If you have a minute, please add a comment when you're done so I know if I'm presenting this well enough.)

C# Custom Events in a Winforms Application

8/23/2010

In this article I show how to raise an event in two ways:

  1. Using the .Net convention of an EventHandler<T> delegate and an EventArgs class.
  2. Ignoring the convention and use a custom delegate type for an event member instead of the EventHandler<T> delegate and eliminating the EventArgs class.

And because lambdas are so important in C# 3.0, I show how you might use an lambda expression instead of a named method for the event handling method in your subscriber class.

If you're here for a quick example, just scroll down to the code and skip the verbiage.

Contents

Events depend on delegates, so if you're not familiar with them, see my article on delegates. For a basic understanding, it may suffice to think of a delegate as a reference to a method. The method can be called with the delegate name instead of the name of the method it refers to. But don't settle for a basic understanding. Delegates are everywhere and a poor grasp of delegates will hold you back every day.

The Example Back to Top

The example has two forms:
  • EventRaiser - the form that raises the event.
  • Subscriber - the form that subscribes to the event
The code for each form is below and you can cut and paste it into a simple winforms app to play with.

Here's what the two forms look like after the user has entered a name and clicked Save in the EventRaiser form. The name Bob shows up in a label on the Subscriber form because when it was instantiated, it's constructor subscribed to an event raised in the EventRaiser form when the Save button was clicked. Don't get confused by the fact that I raise the custom event inside of the Save button event. The two events are completely different and separate. I could have raised the custom event anywhere but for this example it made sense to raise it in the Save button event handler.
Event Demo forms

Here's how the example works:
  • The app starts by instantiating and showing the EventRaiser form
  • The EventRaiser form instantiates and shows the Subscriber form, sending a reference to itself in the constructor.
  • The Subscriber class constructor registers an event handling method with the EventRaiser.
  • Now the two forms are showing.
  • The user enters a name in a textbox on the EventRaiser form and clicks the Save button.
  • The SaveButton_Click event is raised as it's been wired using Visual Studio - this is not our custom event.
  • The SaveButton_Click event method in turn raises our custom event to which the Subscriber form has registered one of it's methods.
  • The event handling method in the Subscriber form displays the name.

Coding It the Conventional Way Back to Top

There are five things to code; three things to raise an event and two things to subscribe to an event. Note that this is for the conventional approach. I'll show an alternative approach later.
  1. I created a custom EventArgs class, used by the EventRaiser class, to pass, (expose), data to subscribers.
  2. In the EventRaiser class, I added an event member.
  3. In the EventRaiser class, I added code to raise the event, aka notify the event subscribers.
  4. In the Subscriber class, I wrote an event handling method to do all the work I want to do when notified by the event.
  5. In the Subscriber class, I wrote the code to register the method with the event in the EventRaiser class.

Here's all the code for the EventRaiser form and the EventArgs class. Just below, I'll break out each of the 3 things to code on the EventRaiser side before moving on the the Subscriber:

The Custom EventArgs and EventRaiser Classes

using System;
using System.Windows.Forms;

namespace CustomEventArgs
{
    public partial class EventRaiser : Form
    {
        // Define an event member of delegate type EventHandler<T>,
        // named NameAddedEvent
        // The type parameter of the generic EventHandler<T> is NameAddedEventArgs.
        // To clarify the terminology:
        // The name of the event is NameAddedEvent.
        // The type of the event member is EventHandler<NameAddedEventArgs>.
        public event EventHandler<NameAddedEventArgs> NameAddedEvent;        

        public EventRaiser()
        {
            InitializeComponent();
            Subscriber subscriber = new Subscriber(this);
            subscriber.Show(); 
        }

        // Call the callback method - "Raise the event" is the proper terminology.
        // MSDN says to use protected virtual so a derived class can handle a base class 
        // event by overriding this method.
        // Probably not an important consideration when dealing with a FORM class
        protected virtual void btnSave_Click(object sender, EventArgs e)
        {
            // The call to raise events should be in a try catch to prevent unhandled 
            // exceptions in the event handler method from crashing the app here.
            string tonsOfData = "This string variable represents lots of data to send";
                       
            try
            {
                // Check for null, in case no other forms have registered with this event.
                if (NameAddedEvent != null)
                    NameAddedEvent(this
                        new NameAddedEventArgs(this.txtName.Text.ToString(), tonsOfData));
            }
            catch(Exception exception)
            {
                MessageBox.Show(exception.Message);
            }
            
        }
    }

    // Define a type derived from EventArgs to hold information that is exposed
    // to the registered event handlers.    
    public class NameAddedEventArgs : EventArgs
    {
        // When a name is added, we want to make it available
        // to the registered event handlers.

        //Public Properties
        public string Name { get { return _name; } }
        public string TonsMoreData { get { return _tonsMoreData; } }

        private readonly String _name;
        private readonly String _tonsMoreData;
       
        // Constructor
        public NameAddedEventArgs(string name, string tonsOfData)
        {
            _name = name;
            _tonsMoreData = tonsOfData;
        }
    }
}

Now I'll break out the three things I needed to code on the EventRaiser side in the order in which I coded them.

1. Code the Custom EventArgs Class Back to Top

At the bottom of the listing you'll see the class NameAddedEventArgs that derives from EventArgs. This class holds the data that you want to expose to the event subscribers. The name of the class follows the .Net convention to name your custom eventargs class, xxxxxEventArgs, where xxxxxx describes what the event is doing. It should have a constructor, and fields and properties to expose the data.

Why does it derive from EventArgs? If you look at the Eventargs class in MSDN, you'll see that there's nothing to it; is has no methods other than what it inherits from Object and no fields or properties. It's just there to support the .Net convention for the signature of event handling methods found in event subscribers. It's also there for the EventHandler<T> Delegate Type, which we'll see in the next section.

The convention, which you've probably seen many many times in winforms apps, is for event handling methods to take two parameters, an object and an EventArgs. For example, here's what Visual Studio generated for the Save button click event:

void btnSave_Click(object sender, EventArgs e)

When we get to the Subscriber class, you'll see that our custom event handler has the same signature.

In my example I'm passing two things, Name and TonsOfData, the latter I've used to simulate a decent amount of data to expose. I do this to highlight that you'd be more likely to use an alternative approach, elimintaing the custom eventArgs class, when you don't have much data to expose. If you do have a fair amount of data to expose, you should probably follow the .Net convention. I show the altenative approach later.

2. Create the Event Member Back to Top

First of all, what is this event member thing anyway? It's hard to tell because the compiler is hiding much of what's going on. It looks like an object of the delegate type, EventHandler<T> and it works to think of it that way, but it's not quite that. Here it is again:

public event EventHandler<NameAddedEventArgs> NameAddedEvent;
There are a couple of things to understand in this one line of code before we can understand what an event member is.

2a. The EventHandler<T> Delegate Type Back to Top

First, if you're not fully up on delegates yet, let's try to clarify what the delegate type, EventHandler<T> is. It's a generic delegate type that comes with the .Net Framework. Here's the definition:
public delegate void EventHandler<TEventArgs> (Object sender, TEventArgs e)
                       where TEventArgs : EventArgs

It's included in the Framework to support the convention I've discussed, where the event handler method receives two arguments, an Object type that is the class that raised the event and an EventArgs type that contains any data that the event is exposing to any subscribers.

The code, where TEventArgs : EventArgs, is called a generic constriant; it's saying that for this generic delegate, the type param TEventArgs must derive from the EventArgs class. To better understand this generic delegate, let's replace all the type parameters in the definition, TEventArgs, with our type, NameAddedEventArgs. The delegate now looks like this:

public delegate void EventHandler<NameAddedEventArgs> (Object sender, NameAddedEventArgs e)

Any eventhandler registering with this event delegate must have this signature; return void and two arguments, Object and EventArgs.

2b. The Event Member Back to Top

Now let's see what this event member thing is. Here it is again, for the third time:

public event EventHandler<NameAddedEventArgs> NameAddedEvent;
Here's the definition of the event keyword from MSDN:
event type declarator;
type - The delegate to which you want to associate this event.
declarator - The name of the event.

At first I figured this would instantiate the delegate type; that an event was really a delegate object. But funny thing: The Delegate class lacks a constructor that takes no arguments, so maybe things were'nt that smiple. Turns out an event is sort of a delegate; you might say it's a delegate once removed.

For a one line event member definition, the compiler creates three things:

  1. A private delegate type field, which references the head of a list of delegates and initialized to null.
  2. An add method that in turn calls the Delegate.Combine when a subscriber registers with the event.
  3. A remove method that in turn calls the Delegate.Remove when a subscriber unsubscribes with the event.
The delegate field is updated each time Combine or Remove is called. Combine has a signature that takes two delegate objects as arguments and returns a new delegate object that has chained the two delegates that were passed in. The delegate field is then updated with the returned delegate object. Remove does the same thing except it removes a delegate from the chain. I'll talk a bit more about this when discussing the code in the subscriber class.

2b1. Custom Accessor Declarations Back to Top

I mentioned that the compiler creates these three things for a one line event declaration. But you can give the event declaration a body and write your own add and remove methods that will maintain a subscriber list using some other mechanism. There's another definiton for the event keyword in MSDN:

event type member-name {accessor-declarations};
accessor-declarations (optional) - Declaration of the accessors, which are used to add and remove event handlers in client code. The accessor functions are add and remove. It is an error to define one but not the other.

So if you provide the optional accessor-declarations, the compiler won't do it's thing. It won't create a delegate field or it's add and remove methods. Controls use this approach because they typically have many events that are of no interest to most subscribers. It'd be a waste to let the compiler create all the delegate fields and add and remove methods. You can either write the code yourself or use System.ComponentModel.EventHandlerList to do the job. This topic is beyond the scope of this article.

2b2. Using a Custom Delegate Type Back to Top

Note that you'll sometimes see a custom event implemented with a custom delegate type instead of the built-in System.EventHandler<TEventArgs> class. The custom delegate type would have the same signature, of course. It would look like this:

public delegate void MyEventHandler(Object sender, NameAddedEventArgs e);

public event MyEventHandler NameAddedEvent;

EventHandler<TEventArgs> has been around since .Net 2.0, so I guess a lot people haven't heard about it or would rather stay away from anything generic until they understand generics better. This gives you hint at how to create an event that doesn't follow convention.

3. Raise the Event Back to Top

NameAddedEvent(this
    new NameAddedEventArgs(this.txtName.Text.ToString(), tonsOfData));

The last thing we do in EventRaiser is add the code that notifies any registered event handlers. That's plural because a delegate type has a field, _invocationList that is a pointer to another delegate object, so you can chain delegate objects together. I'll talk a little more about this later. Inside the btnSave_Click method, is code to invoke the event delegate instance, or to use the terminology around events, notify any registered event handlers. Notice the signature of the call is a void return and two args, this and our EventArgs object. When we call the EventArgs constructor, we send it the name that the user entered in the textbox and the simulated mound of data. Note the check for null first; we don't want to crash if no other form in the app cares about this event. Also notice the try-catch to deal with any unhandled exceptions in any event handlers.

Recap

OK, we're done with the EventRaiser class. There isn't much code and you really don't need to know much about delegates or generics; you can just copy the pattern. But there's a lot to understand in that small amount of code. Three things we had to do:

  1. Define a custom event args class
  2. Create an event member
  3. Notify registered event handlers

The Event Subscriber Back to Top

Here's the code:

using System;
using System.Windows.Forms;

namespace CustomEventArgs
{
    public partial class Subscriber : Form
    {
        public Subscriber()
        {
            InitializeComponent();         
        }

        // Constructor overload for used to subscribe to an EventRaiser event.
        public Subscriber(EventRaiser frmEventRaiser)
        {
            InitializeComponent();

            // Register the callback method with EventRaiser's
            // NameAddedEventHandler
            frmEventRaiser.NameAddedEvent += ProcessNewGroup;         
        }

        // This is the callback method that is registered with EventRaiser's 
        // NameAddedEventHandler. It will grab the custom group name from
        // the custom event args class, e, and process it.
        private void ProcessNewGroup(object sender, NameAddedEventArgs e)
        {
            this.lblGroupName.Text = e.Name;

            // Work, work, work, work, work...
            string working = e.TonsMoreData;
            
            // What if this method throws an unhandled exception?  Since the code that 
            // raises the exception is in a try-catch, over in EventRaiser, the app 
            // doesn't crash. Uncomment the throwing of the exception to see it work.
            //throw new DivideByZeroException("test an exception");
        }        
    }
}

4. Code the Event Handler Method Back to Top

ProcessNewGroup is my event handler. It's signature must match the signature of the EventHandler delegate type; return void and take two arguments, (object, EventArgs). C# delegates are type safe; the method signature of your event handler must match the signature of the delegate type of your event. The compiler will otherwise complain. In this example I'm using a named method because what I'm doing requires more than a line or two of code. All that work, work... simulates the processing. If you don't have much processing to do for an event you might forgo the named method and use a lambda instead. I'll show this in the next example.

5. Register with the Event Back to Top

Notice the compiler shortcut, the += operator overload, used to register the event handler with the event. There's also a -= operator to unregister. You can guess that these operator overloads produce calls to the add and remove methods that compiler generates for the event. If you look for a frmEventRaiser.NameAddedEvent.Add method with intellisense you won't find it. The compiler wants the operators. This is another reason it's easy to think that an event is a delegate; the same operators are used for Delegate.Combine and Delegate.Remove to add and remove delegate instances from a invocation list of a delegate. This is why I describe an event as a delegate once removed: you call the event's add method, using the += operator and the add method in tun calls the Delegate.Combine method.

The registration takes place in the constructor, which takes a reference to the EventRaiser class. You can design your application any way you want as long as you somehow provide a reference to the class that will be raising the event to any potential subscribers. I talked about loose coupling at the beginning and this isn't an example of it. There are all kinds of possibilities including schemes to truly decouple your subscribers from the publishers, like some kind of intermediary between the two.

Coding It the UnConventional Way Back to Top

There are a couple of situations that might prompt you to diverge from the conventional way of coding an event and I've already hinted at what they are.

  • If you don't have much data to expose you can forgo coding the EventArgs class in the EventRaiser, saving you a little coding overhead. Without an EventArgs class you wouldn't use the EventHandler<T> type because it demands the use of an EventArgs class. So you'd define our own delegate type with a signature that fits your needs.
  • If you have very little processing to do in your event handler you can forgo coding a named method in your Subscriber and use a lambda instead.
So, all I mean by unconventional is that you don't use an EventArgs class or the EventHandler<T> delegate.

Sans EventArgs Back to Top

using System;
using System.Windows.Forms;

namespace CustomEventWithCustomDelegate
{
    public partial class EventRaiser : Form
    {
        // Define a delegate type which will support our name added event.
        // Any event handlers will need to have this signature.
        public delegate void NameAdded(string name);

        // Define an event member of delegate type NameAdded
        public event NameAdded NameAddedEvent;

        public EventRaiser()
        {
            InitializeComponent();
            // Create a subscriber form, sending it a reference to myself
            Subscriber subscriber = new Subscriber(this);
            subscriber.Show();
        }

        private void btnSave_Click(object sender, EventArgs e)
        {
            // The call to raise events should be in a try catch to prevent unhandled 
            // exceptions in the event handler method from crashing the app here.
            try
            {
                // Check for null, in case no other forms have registered with this event.
                if (NameAddedEvent != null)
                    NameAddedEvent(this.txtName.Text.ToString());
            }
            catch (Exception exception)
            {
                MessageBox.Show(exception.Message);
            }
        }
    }
}

Notice that the EventArgs class is gone. I like it already; less code. Notice at the top where I've defined a delegate type that has just one string parameter and returns void.

public delegate void NameAdded(string name);

This is the method signature that any methods that register with our event must have. Our NameAdded delegate type takes the place of the EventArgs type we used in the first example. And finally we have to create our event member, just like before. The notification code inside btn_Click is unchanged.

On to the Subscriber class.

Handle It With a Lambda Back to Top

using System;
using System.Windows.Forms;

namespace CustomEventWithCustomDelegate
{
    public partial class Subscriber : Form
    {
        public Subscriber()
        {
            InitializeComponent();
        }

        // Constructor overload for used to subscribe to an EventRaiser event.
        public Subscriber(EventRaiser frmEventRaiser)
        {
            InitializeComponent();
            string capturedVariable = "Processed by the lambda handler: ";

            // Register an anoymous method in Lambda form
            frmEventRaiser.NameAddedEvent +=
                name => this.lblName.Text = capturedVariable + name;
        }       
    }
}

The named method event handler is gone. Since all we're doing takes one line of code, it makes sense to use a lambda and forgo the overhead of a named method. This doesn't really have anything to do with events, but I threw it in for kicks. I also threw in the concept of a captured variable to whet your curiosity. You might start with MSDN's eplanation of captured variables.

 

Decoupling Forms Back to Top

We've seen that to subscribe to an event in another class, the subscriber class needs a reference to an instance of the class that will be raising the event. This reference will bind your classes together but schemes exist for truly decoupling subscribers from publishers, usually some kind of an intermediary between the two. See Martin Fowler's artilce on event aggregators. Or Jeremy Miller's article on event aggregation or Google something like "event aggregation winforms".

I wrote this article to clarify event handling for myself after I had a winforms MDI app where a child form needed to make a change to a property of a control in it's MDI parent. I could have written a method to dig a reference to the control out of the MDIParent's controls collection but a recursive method searching for a control in another form just seems wrong aesthetically and it seems to me that a form should change only it's own controls. I decided to use an event instead, though I'd never written one.

A couple of MSDN links:

Events Tutorial
Events (Programming Guide)

Click a star

Comments

By Rajesh on 12/28/2010 4:09:00 AM
Nice article from you dear. just going on....... Best of Luck. Regard.. Rajesh Marakana Param Software Software Developer

Add your comment: