Search   Articles   Dev Forums   Personalize   Favorites   Member Login   ASP.Net Hosting
DevASP.NET for ASP.NET, VB.NET, XML and C# (C-Sharp) Developers Sunday, July 20, 2008

Search Directory
ASP.NET
VB.Net
C-Sharp
SQL Server
 

Events


Both methods and properties allow us to write code that interacts with our objects by invoking specific functionality as needed. It is often useful for our objects to provide notification as certain activities occur during processing. We see examples of this all the time with controls, where a button indicates it was clicked via a Click event, or a textbox indicates its contents have changed via the TextChanged event.

 

Our objects can raise events of their own – providing a powerful and easily implemented mechanism by which objects can notify our client code of important activities or events. In VB.NET, events are provided using the standard .NET mechanism of delegates. We'll discuss delegates after we explore how to work with events in VB.

Handling Events

We are all used to seeing code in a form to handle the Click event of a button – code such as:

 

Private Sub button1_Click (ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles button1.Click

End Sub

 

Typically we just write our code in this routine without paying a lot of attention to the code created by the VS.NET IDE. However, let's take a second look at that code, since there are a couple of important things to note here.

 

First off, notice the use of the Handles keyword. This keyword specifically indicates that this method will be handling the Click event from the button1 control. Of course, a control is just an object – so what we're indicating here is that this method will be handling the Click event from the button1 object.

 

Also notice that the method accepts two parameters. The Button control class defines these parameters. It turns out that any method that accepts two parameters with these data types can be used to handle the Click event. For instance, we could create a new method to handle the event:

 

Private Sub MyClickMethod(ByVal s As System.Object, _

ByVal args As System.EventArgs) Handles button1.Click

End Sub

 

Even though we've changed the method name, and the names of the parameters, we are still accepting parameters of the same data types and we still have the Handles clause to indicate that this method will handle the event.


 

Handling Multiple Events

The Handles keyword offers even more flexibility. Not only can the method name be anything we choose, but a single method can handle multiple events if we desire. Again, the only requirement is that the method and all the events being raised must have the same parameter list.

 

This explains why all the standard events raised by the .NET system class library have exactly two parameters – the sender and an EventArgs object. By being so generic, it is possible to write very generic and powerful event handlers than can accept virtually any event raised by the class library.

 

One common scenario where this is useful is where we have multiple instances of an object that raises events, such as two buttons on a form:

 

Private Sub MyClickMethod(ByVal sender As System.Object, _

ByVal e As System.EventArgs) _

Handles button1.Click, button2.Click

 

End Sub

 

Notice that we've modified the Handles clause to have a comma-separated list of events to handle. Either event will cause our method to run, giving us a central location to handle these events.

The WithEvents Keyword

The WithEvents keyword tells VB that we want to handle any events raised by the object within our code. For example:

 

Friend WithEvents button1 As System.Windows.Forms.Button

 

The WithEvents keyword makes any events from an object available for our use, while the Handles keyword is used to link specific events to our methods so we can receive and handle them. This is true not only for controls on forms, but also for any objects that we create.

 

The WithEvents keyword cannot be used to declare a variable of a type that doesn't raise events. In other words, if the Button class didn't contain code to raise events, we'd get a syntax error when we attempted to declare the variable using the WithEvents keyword.

 

The compiler can tell which classes will and won't raise events by examining their interface. Any class that will be raising an event will have that event declared as part of its interface. In VB.NET, this means that we will have used the Event keyword to declare at least one event as part of the interface for our class.

Raising Events

Our objects can raise events just like a control, and the code using our object can receive these events by using the WithEvents and Handles keywords. Before we can raise an event from our object, however, we need to declare the event within our class by using the Event keyword.

 

In our Person class, for instance, we may want to raise an event any time the Walk method is called. If we call this event Walked, we can add the following declaration to our Person class:

 

Public Class Person

Private mstrName As String

Private mdtBirthDate As Date


 

Private mintTotalDistance As Integer

Private colPhones As New Hashtable()

Private mintAllergens As Integer

 

Public Event Walked()

 

Our events can also have parameters – values that are provided to the code receiving the event. A typical button's Click event receives two parameters, for instance. In our Walked method, perhaps we want to also indicate the distance that was walked. We can do this by changing the event declaration:

 

Public Event Walked(ByVal Distance As Integer)

 

Now that our event is declared, we can raise that event within our code where appropriate. In this case, we'll raise it within the Walk method – so any time that a Person object is instructed to walk, it will fire an event indicating the distance walked. Make the following change to the Walk method:

 

Public Sub Walk(ByVal Distance As Integer)

mintTotalDistance += Distance

RaiseEvent Walked(Distance)

End Sub

 

The RaiseEvent keyword is used to raise the actual event. Since our event requires a parameter, that value is passed within parentheses and will be delivered to any recipient that handles the event.

 

In fact, the RaiseEvent statement will cause the event to be delivered to all code that has our object declared using the WithEvents keyword with a Handles clause for this event, or any code that has used the AddHandler method.

 

If more than one method will be receiving the event, the event will be delivered to each recipient one at a time. The order of delivery is not defined – meaning that we can't predict the order in which the recipients will receive the event – but the event will be delivered to all handlers. Note that this is a serial, synchronous process. The event is delivered to one handler at a time, and it is not delivered to the next handler until the current handler is complete. Once we call the RaiseEvent method, the event will be delivered to all listeners one after another until it is complete – there is no way for us to intervene and stop the process in the middle.

Receiving Events with WithEvents

Now that we've implemented an event within our Person class, we can write client code to declare an object using the WithEvents keyword. For instance, in our project's Form1 code module, we can write the following:

 

Public Class Form1

Inherits System.Windows.Forms.Form

 

Private WithEvents mobjPerson As Person

 

By declaring the variable WithEvents, we are indicating that we want to receive any events raised by this object.


We can also choose to declare the variable without the WithEvents keyword, though, in that case, we would not receive events from the object as described here. Instead we would use the AddHandler method, which we'll discuss after we cover the use of WithEvents.

 

We can then create an instance of the object, as the form is created, by adding the following code:

 

Private Sub Form1_Load(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles MyBase.Load

 

mobjPerson = New Person()

 

End Sub

 

At this point, we've declared the object variable using WithEvents, and have created an instance of the Person class so we actually have an object with which to work. We can now proceed to write a method to handle the Walked event from the object by adding the following code to the form. We can name this method anything we like – it is the Handles clause that is important as it links the event from the object directly to this method, so it is invoked when the event is raised:

 

Private Sub OnWalk(ByVal Distance As Integer) Handles mobjPerson.Walked

MsgBox("Person walked " & Distance)

End Sub

 

We're using the Handles keyword to indicate which event should be handled by this method. We're also receiving an Integer parameter. If the parameter list of our method doesn't match the list for the event, we'll get a compiler error indicating the mismatch.

 

Finally, we need to call the Walk method on our Person object. Add a button to the form and write the following code for its Click event:

 

Private Sub Button1_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles button1.Click

mobjPerson.Walk(42)

 

End Sub

 

When the button is clicked, we'll simply call the Walk method, passing an Integer value. This will cause the code in our class to be run – including the RaiseEvent statement. The result will be an event firing back into our form, since we declared the mobjPerson variable using the WithEvents keyword. Our OnWalk method will be run to handle the event, since it has the Handles clause linking it to the event.


The following diagram illustrates the flow of control:

 

 

The diagram illustrates how the code in the button's click event calls the Walk method, causing it to add to the total distance walked and then to raise its event. The RaiseEvent causes the OnWalk method in the form to be invoked and, once it is done, control returns to the Walk method in the object. Since we have no code in the Walk method after we call RaiseEvent, the control returns to the Click event back in the form, and then we're all done.

 

Many people have the misconception that events use multiple threads to do their work. This is not the case. Only one thread is involved in this process. Raising an event is much like making a method call, in that our existing thread is used to run the code in the event handler. This means our application's processing is suspended until the event processing is complete.

Receiving Events with AddHandler

Now that we've seen how to receive and handle events using the WithEvents and Handles keywords, let's take a look at an alternative approach. We can use the AddHandler method to dynamically add event handlers through our code.

 

WithEvents and the Handles clause require that we declare both the object variable and event handler as we build our code, effectively creating a linkage that is compiled right into our code. AddHandler, on the other hand, creates this linkage at runtime, which can provide us with more flexibility. Before we get too deep into that however, let's see how AddHandler works.

 

In Form1, we can change the way our code interacts with the Person object – first eliminating the WithEvents keyword:

 

Private mobjPerson As Person

 

and then also eliminating the Handles clause:

 

Private Sub OnWalk(ByVal Distance As Integer)

MsgBox("Person walked " & Distance)

End Sub


With these changes, we've eliminated all event handling for our object and so our form will no longer receive the event, even though the Person object raises it.

 

Now we can change the code to dynamically add an event handler at runtime by using the AddHandler method. This method simply links an object's event to a method that should be called to handle that event. Any time after we've created our object, we can call AddHandler to set up the linkage:

 

Private Sub Form1_Load(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles MyBase.Load

mobjPerson = New Person()

AddHandler mobjPerson.Walked, AddressOf OnWalk

End Sub

 

This single line of code does the same thing as our earlier use of WithEvents and the Handles clause – causing the OnWalk method to be invoked when the Walked event is raised from our Person object.

 

However, this linkage is done at runtime, and so we have more control over the process than we have otherwise. For instance, we could have extra code to decide which event handler to link up. Suppose we have another possible method to handle the event in the case that a message box is not desirable. Add this code to Form1:

 

Private Sub LogOnWalk(ByVal Distance As Integer)

System.Diagnostics.Debug.WriteLine("Person walked " & Distance)

End Sub

 

Rather than popping up a message box, this version of the handler logs the event to the Output window in the IDE.

 

Now we can enhance our AddHandler code to decide which handler should be used – dynamically
at runtime:

 

Private Sub Form1_Load(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles MyBase.Load

mobjPerson = New Person()

If Microsoft.VisualBasic.Command = "nodisplay" Then

AddHandler mobjPerson.Walked, AddressOf LogOnWalk

Else

AddHandler mobjPerson.Walked, AddressOf OnWalk

End If

End Sub

 

If the word nodisplay is on the command line when our application is run, the new version of the event handler will be used – otherwise we'll continue to use the message box handler.



DevASP.Net - Disclaimer - Privacy
Copyright © 2008 DevASP.net