Custom events

In my previous entry on events (written well before even VS 2002 had shipped), I made the comment:

VB does not have a syntax for defining events that allows you to specify the field, the add method or the remove method. Those are always implicitly generated.

Now, most of the time this doesn’t really matter. Most of the time, the code you write in the add and remove method is going to be the same boilerplate code over and over and over again, so you’re going to want to just let the compiler do its thing and not worry about it too much. However, there are some situations in which you might want to take over managing an event’s delegate. The most common case that I know of is the situation in which you have an object that raises a lot of events. For example, a Form can raise something like 85 different events. If you accept the default compiler behavior, this means that the compiler will generate a field for each and every event to store the event handlers for that event. Which means in the case of Form, that it would generate something like 85 fields, even though in most cases programmers only ever handle about 4-5 events on a Form!

One alternative to wasting all that space is to use a hashtable to store delegates for just the events that someone is handling. To do this, though, you need to be able to control what happens when someone hooks up to or unhooks from an event. So, in VB 2005, we’re introducing something we call custom events that look something like this:

Class C1
    Public Custom Event MyEvent As EventHandler
        AddHandler(ByVal d As EventHandler)
            ...
        End AddHandler

        RemoveHandler(ByVal d As EventHandler)
           ...
        End RemoveHandler

        RaiseEvent(ByVal o As Sender, ByVal e As EventArgs)
            ...
        End RaiseEvent
    End Event
End Class

Custom events are declared with the Custom modified on the event declaration and have to explicitly state their delegate type. Custom events have three parts: an AddHandler method that is called when someone is hooking up to the event, a RemoveHandler method that is called when someone unhooks from the event and a RaiseEvent method that is called when the class does a RaiseEvent on the event. The AddHandler and RemoveHandler methods take a delegate of the type of the event. The RaiseEvent method takes the same parameters as the event delegate does. So, to store all event delegates in one hashtable, you could do the following:

Class C1
    Private EventDelegates As New Dictionary(Of String, EventHandler)

    Private Sub AddNewHandler(ByVal eventName As String, ByVal handler As EventHandler)
        If EventDelegates.ContainsKey(eventName) Then
            EventDelegates(eventName) = CType([Delegate].Combine(EventDelegates(eventName), handler), EventHandler)
        Else
            EventDelegates(eventName) = handler
        End If
    End Sub

    Private Sub RemoveExistingHandler(ByVal eventName As String, ByVal handler As EventHandler)
        If EventDelegates.ContainsKey(eventName) Then
            EventDelegates(eventName) = CType([Delegate].Remove(EventDelegates(eventName), handler), EventHandler)
        End If
    End Sub

    Private Sub RaiseOneEvent(ByVal eventName As String, ByVal sender As Object, ByVal e As EventArgs)
        If EventDelegates.ContainsKey(eventName) Then
            Dim p As EventHandler = EventDelegates(eventName)

            If p IsNot Nothing Then
                p.Invoke(sender, e)
            End If
        End If
    End Sub

    Public Custom Event MyEvent As EventHandler
        AddHandler(ByVal d As EventHandler)
            AddNewHandler("MyEvent", d)
        End AddHandler

        RemoveHandler(ByVal d As EventHandler)
            RemoveExistingHandler("MyEvent", d)
        End RemoveHandler

        RaiseEvent(ByVal sender As Object, ByVal e As EventArgs)
            RaiseOneEvent("MyEvent", sender, e)
        End RaiseEvent
    End Event
End Class

One thing to notice that’s different from C# is that we make you specify a RaiseEvent method. This is to enable the RaiseEvent statement, which C# doesn’t have, to work properly. Otherwise, it works pretty much the same way C#’s event declaration does.

16 thoughts on “Custom events

  1. Fan

    Why doesn’t we put these code in the RaiseEvent accessor directly?

    If p IsNot Nothing Then

    p.Invoke(sender, e)

    End If

    So that we can write the event codes in same parttern with the normal event statement.

    Reply
      1. Fan

        Oh, I mean that in your code we can use RaiseEvent to raise the custom event. However, in previous version of Visual Basic I allways create an ‘OnEventName’ virtual procedure in which I put the ‘RaiseEvent’ statement and call this procedure when I want to fire it. So we can remain this pattern in the new custom event. I mean let the ‘RaiseEvent’ accessor do the same task just like original event statement, and then use the OnEventName procedure to raise the event. I want to know whether your pattern have other advantages so that I can use for reference. 🙂

        Reply
        1. paulvick

          Fan, sorry for the slow response… You can still use the existing RaiseEvent/OnEventName pattern that you use today with this if you like. There’s no reason not to.

          Reply
  2. Pingback: Anonymous

  3. Pingback: ISerializable

  4. Pingback: ISerializable

  5. Richard

    In v1, C# uses the System.ComponentModel.EventHandlerList class to store the delegates for the handled events. A large part of the BCL uses this class, particularly anything derived from System.ComponentModel.Component, which is the base class for every Windows Forms control, and anything derived from System.Web.UI.Control, which is the base class for all ASP.NET controls.

    Since all of these cases provide access to the list via the protected Events property, what advantage would there be in declaring a separate private instance of Dictionary(Of String, EventHandler) to store the delegates? Even if your class doesn’t derive from one of these base classes, it would make more sense to keep a consistent approach to your events.

    Class C1

    Private _Events As System.ComponentModel.EventHandlerList

    Protected ReadOnly Property Events As System.ComponentModel.EventHandlerList

    Get

    If _Events Is Nothing Then

    _Events = New System.ComponentModel.EventHandlerList()

    End If

    Return _Events

    End Get

    End Property

    Protected Sub AddEventHandler(ByVal key As Object, ByVal handler As Delegate)

    If key Is Nothing Then Throw New ArgumentNullException("key")

    If handler IsNot Nothing Then

    Events.AddHandler(key, handler)

    End If

    End Sub

    Protected Sub RemoveEventHandler(ByVal key As Object, ByVal handler As Delegate)

    If key Is Nothing Then Throw New ArgumentNullException("key")

    If handler IsNot Nothing AndAlso _Events IsNot Nothing Then

    Events.RemoveHandler(key, handler)

    End If

    End Sub

    Protected Sub RaiseOneEvent(ByVal key As Object, ByVal sender As Object, ByVal e As EventArgs)

    If key Is Nothing Then Throw New ArgumentNullException("key")

    If _Events IsNot Nothing Then

    Dim d As Delegate = Events[key]

    If d IsNot Nothing Then

    If sender Is Nothing Then sender = Me

    If e Is Nothing Then e = EventArgs.Empty

    d.DynamicInvoke(New Object() {sender, e})

    End If

    End If

    End Sub

    Private Shared Object MyEventKey = New Object()

    Public Custom Event MyEvent As EventHandler

    AddHandler(ByVal d As EventHandler)

    AddEventHandler(MyEventKey, d)

    End AddHandler

    RemoveHandler(ByVal d As EventHandler)

    RemoveEventHandler(MyEventKey, d)

    End RemoveHandler

    RaiseEvent(ByVal sender As Object, ByVal e As EventArgs)

    RaiseOneEvent(MyEventKey, sender, e)

    End RaiseEvent

    End Event

    End Class

    Reply
  6. Pingback: Panopticon Central

  7. A Basuri

    Hi

    I’m trying to create custom event in a custom control and will later convert it as an anthem-ised custom control to use that. Actually my requirement is such a control which populates itself according to the data stored in database. e.g. there are 5 Product groups then the control has some property (like “NumberOfProducts” ) which then populates itself as a product groups having 5 products i.e inside render event of that control i’m creating a table with 5 <td> tags containing a single table in each of those <td>, having 3 rows for ProductName, productImage and ProductDescription. When I click any of those Product tables say product groups it will then populate its corresponding Product Sub-groups according to its ProductGroupID in a similar way as of product group and on clicking on any of those product subgroups the same control will populate the product according to the selected product SubGroupId. To do this I created some properties. i.e ProductDepth, ProductID but need a custom event as OnProductClick for my control so that I will get some space to do my work with in that event. I have created that event but it’s not firing that event .

    ==========

    Control code

    ==========

    // Defines the Click event.

    public event EventHandler ProductClick;

    // Invokes delegates registered with the ProductClick event.

    protected virtual void OnProductClick(EventArgs e)

    {

    if (ProductClick != null)

    {

    ProductClick(this, e);

    }

    }

    // Method of IPostBackEventHandler that raises change events.

    public void RaisePostBackEvent(string eventArgument)

    {

    this.OnProductClick(EventArgs.Empty);

    }

    I’m creating that control instance dynamically on pageclass level(not in page load event)

    MyControl objProductFrm = new MyControl();

    And on prostback, inside a button click event I’m populating the control and attaching the event with that control as below

    =========

    Button click event code

    =========

    objProductFrm.NumberOfProducts=Convert.ToInt32(TextBox1.Text.ToString());

    objProductFrm.ProductDepth=ProductDepth.ProductGroups;

    this.objProductFrm.ProductClick +=new System.EventHandler(this.objProductFrm_ProductClick);

    But the event is not firing.

    Thanks

    A Basuri

    Reply
  8. A Basuri

    On the page code behind i have written the code to handle the raised event as below .

    private void objProductFrm_ProductClick(object sender, System.EventArgs e)

    {

    string test = e.ToString();

    Response.Write("Product : " + e.ToString().Substring(e.ToString().LastIndexOf("_")+1));

    }

    But the control is not raising the ProductClick event.

    Thanks

    A Basuri

    Reply
  9. Pingback: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *