Xtreme Visual Basic Talk

Xtreme Visual Basic Talk (http://www.xtremevbtalk.com/)
-   Tutors' Corner (http://www.xtremevbtalk.com/tutors-corner/)
-   -   Creating Your Own Events (http://www.xtremevbtalk.com/tutors-corner/298600-creating-own-events.html)

AtmaWeapon 07-13-2008 04:47 PM

Creating Your Own Events
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.

event source
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.

event subscriber
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:
  1. They describe what parameters and return value a class of methods have.
  2. They can be used to call any method that matches their description, even if you don't know the method's name.

event handler
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>)]
<eventName> 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.
  1. 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:

    Sub <name>(ByVal sender As Object, ByVal e As EventArgs)
    This is one of the two choices I recommend.
  2. 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:

    Sub <name>(ByVal sender As Object, ByVal e As T)
    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.
  3. 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.
  4. 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 and Control.MouseDown 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's Click event. Button.Click 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)

End Sub

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's Click event, you would type the line:

AddHandler Button1.Click, AddressOf HandleButtonClick
The AddressOf 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!

AtmaWeapon 07-13-2008 04:48 PM

1 Attachment(s)
How do I define my own events?
This is actually pretty easy, especially if you keep the conventions discussed above in mind. There's some conventions that are to be followed when you make your own events, and if you follow them your events will be easier for other programmers to understand. There's 6 steps to follow to create your own event.

Step 1: Name the event
This is usually simple. Think of a name for the event. Usually, the name is something that could be used for the answer to the question, "What happened?". For example, some of the events on the Form class are FormClosing, SizeChanged, and Load. Each of these events describes what happens to cause the event to be raised. For the purposes of this example, let's say we are going to name our event SomethingHappened, despite the fact that this is a horrible name since it's very vague.

Step 2: Decide if you need a custom EventArgs
Does the fact that your event happened give the subscriber enough information, or do you need to provide more information? If you don't need to provide any custom information, go on to step 3. If you need to provide information to accompany the event, then you'll need to define your own custom EventArgs-derived class for the event, so go to step 2a. If you're confused, consider the MouseUp event as an example: when this event is raised, the user might need to know what button was released and where the button was released. This information can't be provided by EventArgs, so it is passed via the MouseEventArgs class. If you need to pass information like this, go to step 2a.

Step 2a: Define your custom EventArgs
You need a custom class to provide information about the event. This class must ultimately derive from EventArgs, or you won't be able to match the EventHandler(Of T) delegate. There are a few rules you should follow for this class:
  • Name the class <eventName>EventArgs; for the SomethingHappened event this should be named SomethingHappenedEventArgs. This makes the purpose of the class and its connection to the event clear. The exception is if your EventArgs class is going to be used for multiple events; in this case, name it something that clearly relates it to the class of events that use it. A good example is MouseEventArgs, which is used for events that are raised by mouse activity.
  • Express the information in the class as read-only properties. The EventArgs should only have writable properties if you need subscribers to send some information back to the event source; CancelEventArgs is a good example of a class that does this.
  • Provide a constructor that lets the caller set each property. Since the information in the class is supposed to be read-only, this is the only chance to set the properties of the class. Be sure to call the base-class constructor, particularly if you don't derive directly from EventArgs (for example, if you derive from CancelEventArgs you have to provide the parameters for its constructor and call it.)

Now that you have your custom EventArgs class, you can move on to step 3.

Step 3: Declare the event
Now you need to declare your event in the event source class. Look back to the section about how the events work for more guidance here. For our SomethingHappened event, assuming we went through step 2a, the event declaration might look like this:

Public Event SomethingHappened As EventHandler(Of SomethingHappenedEventArgs)
You don't have to make events public, but keep in mind that if the event is not public then you are limiting who can subscribe to the events. Private events are not necessarily a bad idea, but it's generally more clear to call a private method.

Step 4: Declare an OnEvent method
This is not required, but is part of the general event-declaring conventions in the .NET framework. You need to define a method in your event source that is named On<eventName> and takes the appropriate EventArgs parameter. This method should be Protected if you want derived classes to be able to override it or Private if you don't want derived classes to fool with it. Don't make the On<eventName> method public, or else any other class can cause your event to be raised for no reason!

This method is responsible for raising the event, and performing any housekeeping that goes along with it. For example, if your class wants to do something before or after an event is raised, you can make sure to put that code before or after the RaiseEvent in this method.

Here's an example OnSomethingHappened:

Private Sub OnSomethingHappened(ByVal e As SomethingHappenedEventArgs)
    RaiseEvent SomethingHappened(Me, e)
End Sub

You can find lots of methods like this on the Form class. For example, to handle the Load event, you can override the OnLoad method:

Protected Overrides Sub OnLoad(ByVal e As EventArgs)
End Sub

It is very important that you call the base class OnEvent method if you override it. The base class method is responsible for raising the event, and your override method is the one that will be called. If you do not call the base class method yourself, it will never get called and the event will not be raised! Occasionally, this is desirable and you will do it purposefully, but in general it's not a good thing.

Step 5: Raise the event when needed.
Now, you have the framework to raise the event in place. You need to put calls to OnEvent in your code whenever you need the event to be raised. If you have a custom EventArgs class, you'll need to create an instance to pass along with the event. To raise the SomethingHappened event, our code might look like this:

Dim args As New SomethingHappenedEventArgs("explosion")

Step 6: Subscribe to the event.
Now, your event source class will notify any subscribers whenever the event is raised. The only thing left to do is to have event handlers subscribe to the event! See the section titles, "How do I subscribe to Events?" for more information.

I've attached a zip file that contains an example project for VS 2008 and VS 2005. The VS 2005 solution is named "EventDemo.2005.sln". It MIGHT not compile in VS 2005 right off of the bat; I don't have VS 2005 installed to test, and there's some syntax that VS 2008 lets by even if you target .NET Framework 2.0 that doesn't fly in VS 2005. Please let me know if it doesn't compile and I'll fix it. The Counter class is the event source. It has a Count method that counts from 1 to the number you specify. The CountUpdate event is a custom event that is raised whenever the Count method counts a new number. CountUpdate needs to notify its subscribers of the number that was counted, so it uses CountUpdateEventArgs instead of EventArgs. MainForm creates a Counter and calls Count on a new thread after subscribing to the CountUpdate event (a thread is necessary so the UI doesn't lock up.) When the event is raised, the event handler MainForm.HandleCounterCount is called; it adds a timestamp to the number that's counted and calls the UpdateTextBox method. UpdateTextBox does some threading acrobatics, but ultimately updates the text box every time the event is raised. This should give you a good idea of what defining an event looks like.

But wait! There's more!
A few months ago, I posted an Advanced Events Tutorial. It covers some problems with the way VB .NET sets up your events by default, and discusses some advanced syntax for solving these problems. You probably won't need the information in that thread unless you're making classes that should be thread-safe or with more than about 5 or 6 events, but if you feel adventurous you should give it a look.

All times are GMT -6. The time now is 01:19 PM.

Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Search Engine Optimisation provided by DragonByte SEO v2.0.15 (Lite) - vBulletin Mods & Addons Copyright © 2018 DragonByte Technologies Ltd.
All site content is protected by the Digital Millenium Act of 1998. Copyright©2001-2011 MAS Media Inc. and Extreme Visual Basic Forum. All rights reserved.
You may not copy or reproduce any portion of this site without written consent.