This is a tutorial about adding custom events to your classes. It's a beginner-level tutorial, but I do expect you to have a little bit of experience with VB .NET. I'm going to start by discussing what events are. Then, I'm going to discuss some event conventions and how you subscribe to events. Then, I'm going to discuss why you might want to define custom events on your classes. Finally, I'll discuss an example project that uses a custom event.
What are events?
The MSDN article on Events and Delegates defines an event as follows:
An event is a message sent by an object to signal the occurrence of an action.
This is pretty accurate. Events are a special mechanism that lets a class notify other classes when something has happened. Other classes can indicate they are interested in an event and have a particular method called whenever the event is raised. Before things get too much more confusing, let's discuss some terms.
The term to describe when an event happens is that the event is raised
. Some people use "triggered" or "fired", and while these terms do have similar meaning they are considered incorrect by the .NET design guidelines to maintain consistency. Events are raised.
The event source
is the class that raises the event.
When an object subscribes
to an event, the object is telling the event source that it is interested in being notified when the event is raised.
Any object that subscribes to an event source's event is an event subscriber
This is a concept that is a tutorial in itself. For the purposes of this tutorial, you need to know two things about delegates:
- They describe what parameters and return value a class of methods have.
- They can be used to call any method that matches their description, even if you don't know the method's name.
In my opinion, this should be "subscriber method". When an event is created, it declares a delegate that describes the messages that can act as the event handler
for the event. Event subscribers provide an event handler method, and this method is called whenever the event source raises the event.
How do events work?
An event source defines an event using a syntax that looks like this:
Event <eventName> [As EventHandler|As Delegate|(<signature>)]
is the name of the event. After declaring the name, you have to declare the delegate that event subscribers must provide to handle the event. You have four choices here.
- You can say that the event follows the EventHandler delegate. This means that the event handlers that can subscribe to the event will look like this:
This is one of the two choices I recommend.
Sub <name>(ByVal sender As Object, ByVal e As EventArgs)
- You can say the event follows the EventHandler(Of T) delegate. This means the event handlers that can subscribe to the event will look like this:
In this case, T must be some class that derives from EventArgs. This form of event is most useful when you have information you want to send to subscribers when the event is raised. This is the second choice I recommend you make.
Sub <name>(ByVal sender As Object, ByVal e As T)
- You can define a delegate that describes the event handlers that can subscribe to the event, then say this event uses the delegate. This is the .NET 1.x equivalent of using EventHandler(Of T); .NET 1.x had no support for generics. I recommend against using this in .NET 2.0 because it's easier to use EventHandler(Of T), but in .NET 1.1 you have to use it.
- You can define your own parameters for the event handlers. I strongly recommend you never do this. Every event in the .NET Framework follows one of the above two patterns, and if you break this convention then your types will be harder for other people to understand and use.
Why is the EventHandler
convention so important? The most obvious reason is "every event in the .NET framework follows this convention." There's reasons for this, though. The sender
parameter is important because it lets you know what class raised the event. This doesn't sound useful, but it promotes reuse of event handlers; if you have 10 radio buttons and you want to do something when each radio button is checked, you can make one event handler and determine which button was checked by using the sender
parameter. If you didn't have this parameter, you would have to have 10 different event handlers, one for each button. The EventArgs
-derived parameter is important for the same reasons. Every control's Click
event uses EventArgs
as its second parameter. This means you can have a menu and a button that use the same event handler for their Click
event; otherwise you'd have to have different event handlers for each control. Another example is when events share types of information; the Control.MouseUp
events both use MouseEventHandler
, which uses MouseEventArgs
as its second parameter. Both events require information about the mouse, so they share an event handler delegate.
Once the event is declared, the event source class simply uses the RaiseEvent
keyword to raise the event. For a standard EventHandler
class, it might look like this:
RaiseEvent EventName(Me, New EventArgs())
Notice that you have to pass the appropriate parameters for the event handler type. When an event is raised, VB .NET checks to see if any objects have subscribed to the event (C# users have to do this step themselves). If there are any subscribers, the program calls each subscriber and passes the parameters that were provided to each method.
How do I subscribe to events?
I'll use the Click
event as an example. Let's assume you want to subscribe to a button named Button1
follows the EventHandler
delegate, so the first thing you need to do is declare a compatible method to handle the event:
Sub HandleButtonClick(ByVal sender As Object, ByVal e As EventArgs)
This is the event handler method. Now, you have two ways to subscribe to the event in VB .NET.
For the first way, the Button1
variable has to be declared a special way. If it's declared with the WithEvents
keyword, you can indicate that the event handler subscribes to the event using the Handles
keyword. By default, all controls created by the Windows Forms designer are declared using WithEvents
. The variable would look like this:
Private WithEvents Button1 As Button
You can make the event handler method subscribe to this event by making the following change:
Sub HandleButtonClick(...) Handles Button1.Click
This is how the designer sets up event handlers, and it's typical for control event handlers to be set up in this manner.
The second way is done entirely in code, and doesn't require the variable to be declared WithEvents
or anything special to be done to the event handler method. At some point in your code, you use the AddHandler
keyword to subscribe to the event. For example, to say, "I'd like HandleButtonClick
to subscribe to Button1
event, you would type the line:
AddHandler Button1.Click, AddressOf HandleButtonClick
word may look a little weird. The second parameter to AddHandler
must be a delegate that matches the event's delegate; in VB AddressOf
basically means "use this method as a delegate instead of a method call". Usually, you'd set up event handlers using AddHandler
in a constructor or a setup event such as Form.Load
, but you can use it anywhere if you want to handle events on-the-fly. Be warned; if you call AddHandler
twice using the same event source, event, and event handler, the event handler will be called twice each time the event is raised! There is a RemoveHandler
keyword that complements AddHandler
, and there is no error if you try to remove an event handler that is not subscribed. So, if you're adding an event handler on-the-fly, but you don't know for sure if the handler is already subscribed, it's perfectly safe to write code like this:
RemoveHandler Button1.Click, AddressOf HandleButtonClick
AddHandler Button1.Click, AddressOf HandleButtonClick
One important final point: if you manually subscribe to an event using the AddHandler
keyword, when you are done with the event source make sure to unsubscribe using the RemoveHandler
keyword. Event handlers can trick the garbage collector into thinking an object still has references, which means the object will never get collected!