Home > .NET 2.0, C# 2.0 > Event Handling made easy

Event Handling made easy

July 11, 2007

UPDATE NOTICE: This approach has been updated to take advantage of the EventHandler<T> generic delegate. The updated approach is covered in Event Handling made even easier.


If you have been around .NET for a while, you have probably already discovered Events and may be confident in their application. For me, it is something that I inherently understand but have not used enough to remember all the pieces from scratch. As a result, every time I find a situation that I think calls for Event Handling, I have to relearn the physical process of it from scratch. So I decided to write this little tutorial so I would always have it laid out for myself in one place.

Why Events are so Cool

Events have some really cool benefits. First of all, they can be wired into any object. This lets any object report information about itself. Secondly, the events can be monitored or ignored – it is up to other objects to listen or not. The uses of this are pretty much infinite, but I’ll lay out two that I think will make sense for just about all of us.

State Reporting – Imagine a class that encapsulates a record in a database. Now imagine you have a method that reports whether or not the data is “dirty” (meaning that some data has changed and needs to be updated to the database). In order to make use of this information, you need to run the CheckForIsDirty method whenever the object may have changed. This could be a lot of calls to the same method, and could easily be missed in your code. Events offer us another approach. If the object did the checking itself (say in the Set property for each data element) it could fire an IsDirtyChangedEvent whenever the state changes from true to false or vice-versa. Now in the object that needs the information – say to set the Enabled state of a Save button – you simply wire up a single event handling method to the object.

Error Reporting – Similarly, you could use an Event to report errors, rather than the typical Exceptions being thrown and caught. For one thing, while Exception handling is robust, it is also kind of a pain. In order to do it well, you should handle every Exception that could be thrown. This is tedious and the Exception code you write is rarely used. Consider also that a lot of us (myself included) get lazy with Exceptions and just use a standard catch (System.Exception ex) style catch block. This of course defeats the purpose of the robust exception handling offered by .NET. But in those cases, what we really want to know is if any error occurred at all. But the worst of all is when the unexpected exception occurs… you know, the one you don’t plan for that never shows up in testing but that the user seems to find no problem.

So now, let’s consider the same object from above, an encapsulating database record object. Potentially, we could have all kinds of errors caused by invalid data. Simple issues like code-driven fields or too many decimal places, etc. etc. Or it could even be a complex issue with multiple fields having different rules based on the values of other fields. Do you really want to throw (and therefore need to catch) every possible Exception? Sometimes yes, sometimes no. If you are looking for a simple “catch-all” (forgive the pun please) error reporting system, you could create a simple MyObjectErrorOccurredEvent. Throw this event where ever you would have thrown an Exception. Again, in your parent object, you can define a single method to handle all the errors.

These are just a couple examples of things you can do. In the Sketching tool I wrote about recently, I used an Event to notify the parent when each sketch had been created. The parent then updated a counter and reported the progress back through a delegate to the GUI thread. Very slick, and fairly easy.

Enough already, how do I do it?

Fair enough. This is my simple method of doing Events. There are five steps, which I’ll outline in order.

Step 1: Outside of any Class, create a public delegate to define the EventHandler signature.

public delegate void IsDirtyChangedEventHandler(object sender, IsDirtyChangedEventArgs e);

The (object sender, xxxEventArgs e) signature ought to be very familiar to you. This is the basic signature you get for every button_pressed, on_click, mouse_up, etc. etc. event. In our case, we have a custom xxxEventArgs class that we are going to create. Feel free to replace this with any of the standard EventArgs, so long as they will suit your purpose. I find that they usually don’t so creating our own is the way to go.

Step 2: Create our custom EventHandler class. Again, this is optional if you choose a standard EventArgs class. This class is very simple, just a collection of values, really. In our case, it is just going to be one value: a boolean representing whether or not the object IsDirty.

public class IsDirtyChangedEventArgs
{
    private bool _isDirty;

    private IsDirtyChangedEventArgs()
    {
    }

    public IsDirtyChangedEventArgs(bool isDirty)
    {
        _isDirty = isDirty;
    }

    public bool IsDirty
    {
        get { return _isDirty; }
    }
}

Step 3: In the class that is firing the Event, we need to define the event so that other objects can handle the event.

public event IsDirtyChangedEventHandler IsDirtyChangedEvent;

Note that while the Type is a IsDirtyChangedEventHandler, we traditionally leave Handler off the name.

Step 4: Fire the Event. This is pretty straight-forward: when ever you reach the right place in your code, fire the event.

public bool IsDirty
{
    get
    {
        return _isDirty;
    }
    private set
    {
        if (_isDirty != value)
        {
            if (IsDirtyChangedEvent != null)
            {
                IsDirtyChangedEvent(this, new IsDirtyChangedEventArgs(value));
            }
        }
        _isDirty = value;
    }
}

In this case, I have used a private Set property to set the internal variable. Before I update it, I see whether or not it has changed, and if it has then I Fire the event. This way (as long as I always set it using the Property and not accessing the variable itself), the notification is always sent out regardless of what triggered the change. Note that we check to see if the Event is NULL prior to attempting to fire it. If there is no method registered to handle the event, the Event call itself will result in a NullReferenceException.

Notice that I have also employed our custom IsDirtyChangedEventArgs class when I fired the event. If the EventArgs object was any more complicated, I would probably have put it on its own line first.

Step 5: Wire the event up in the parent. Now that I have the Event defined and firing, all I need to do now is add a listener to capture the event in my parent object. There are two parts to this: first you have to register the Event listener with the object.

MyObj _myObj = new MyObj();
_myObj.IsDirtyChangedEvent += new IsDirtyChangedEventHandler(_myObj_IsDirtyChangedEvent);

Of course, VisualStudio and Intellisense will write most of this very ugly signature for you. Even better, once created it will allow you to press TAB and it will create part 2, the actually handler event.

private void _myObj_IsDirtyChangedEvent(object sender, IsDirtyChangedEventArgs e)
{
    this.SetSaveButton(e.IsDirty);
}

As you can see, I have added the code I want to execute whenever the change event is fired.

Conclusions

And that is my simple Event handling method. They really are quite simple, and the sky’s the limit as far as their usefulness goes. So if you haven’t done it before, do it now! If you have done some Events, feel free to comment on my methods above.

Advertisements
Categories: .NET 2.0, C# 2.0
%d bloggers like this: