Basic Instincts

Programming Events of the Framework Class Libraries

Ted Pattison

Contents

The EventHandler Delegate
Customized Event Parameters
Parameterizing Custom Events
Wrap-up

This month's installment represents the final column in a series of three focusing on programming events. In the previous two columns, I showed you how to define and raise events (see Basic Instincts: Programming with Events Using .NET and Basic Instincts: Static Event Binding Using WithEvents). I also explained how to wire up event handlers using both dynamic and static event binding. This month I am going to conclude my coverage of events by showing some practical examples of handling some of the more commonly used events in the Microsoft® .NET Framework.

The EventHandler Delegate

When you build applications using Windows® Forms or ASP.NET, you'll observe that a significant percentage of the events you encounter are defined in terms of a generic delegate type named EventHandler. The EventHandler type exists in the System namespace and has the following definition:

Delegate Sub EventHandler(sender As Object, e As EventArgs)

The delegate type EventHandler defines two parameters in its calling signature. The first parameter, named sender, is based on the generic Object type. The sender parameter is used to pass a reference that points to the event source object. For example, a Button object acting as an event source will pass a reference to itself when it raises an event based on the EventHandler delegate type.

The second parameter defined by EventHandler is named e and is an object of type EventArgs. In many cases, an event source passes a parameter value equal to EventArgs.Empty, indicating there is no additional parameter information. If an event source wants to pass extra parameterized information in the e parameter, it should pass an object created from a class that derives from the EventArgs class.

Figure 1 shows an example involving two event handlers in a Windows Forms application that are wired up using static event binding. Both the Load event of the Form class and the Click event of the Button class are defined in terms of the delegate type EventHandler.

Figure 1 Using Static Event Binding

Imports System
Imports System.Windows.Forms

Public Class MyApp : Inherits Form

    '*** static event handler for base class event
  Private Sub Form1_Load(ByVal sender As Object, _
                         ByVal e As EventArgs)_
                         Handles MyBase.Load
    '*** event handler code
  End Sub

    '*** button defined as WithEvents field
  Friend WithEvents cmdDoTask As Button
  
    '*** static event handler for button
  Private Sub cmdDoTask_Click(ByVal sender As Object, _
                              ByVal e As System.EventArgs) _
                              Handles cmdDoTask.Click
    '*** event handler code
  End Sub

End Class

You should also note that the names and format of the two event handler methods in Figure 1 are consistent with what is generated for you by the Visual Studio® .NET IDE. For example, if you double-click on a form or command button while you are in design view, Visual Studio .NET will automatically create the skeleton of event handler methods that looks like this. All you are required to do is fill in the implementation for these methods to give your event handlers the desired behavior.

You might have noticed that the Visual Studio .NET IDE generates handler methods using the naming scheme that was required by Visual Basic® 6.0. However, you should remember that the names of handler methods don't really matter with static event binding in Visual Basic .NET. It's the Handles clause that matters. You should feel free to rename handler methods to anything you want.

It's possible to rewrite these two event handlers so that they are wired up using dynamic event binding instead of static event binding. For example, the Form-derived class in Figure 2 provides the exact same event binding behavior as the Form-derived class in Figure 1. The only difference is that the latter example uses dynamic event binding and does not require either the WithEvents keyword or the Handles keyword. In many cases, you will write implementations for handler methods based on the EventHandler delegate type without referencing either the sender parameter or the e parameters. For example, these parameter values are of no real use when you are writing a handler for the Load event of a Form-derived class. The sender doesn't provide any value because it simply passes your Me reference. The e parameter passes EventArgs.Empty:

Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        '*** these tests are always true
        Dim test1 As Boolean = sender Is Me
        Dim test2 As Boolean = e Is EventArgs.Empty
End Sub

Figure 2 Using Dynamic Event Binding

Imports System
Imports System.Windows.Forms

Public Class MyApp : Inherits Form
  Friend cmdDoTask As Button
   
  Sub New()
    '*** other initialization code omitted
    AddHandler MyBase.Load, AddressOf Me.Handler1
    AddHandler cmdDoTask.Click, AddressOf Me.Handler2
  End Sub  
  
  Private Sub Handler1(ByVal sender As Object, ByVal e As EventArgs)
    '*** event handler code
  End Sub

  Private Sub Handler2(ByVal sender As Object, _
                       ByVal e As System.EventArgs)
    '*** event handler code
  End Sub
End Class

You might wonder why the calling signature of the Load event isn't more customized for its needs. After all, it would be less confusing if the Load event didn't include any parameters at all. It's fairly easy to find other examples of events based on the EventHandler delegate type in which the sender parameter or the e parameter don't pass anything of value.

Ask yourself the following questions. Why do you think so many events have been modeled in terms of the EventHandler when this delegate type has such a generic calling signature? Why didn't the designers of the .NET Framework model each event in terms of a custom delegate with a calling signature that was fine-tuned for its needs? As it turns out, there was a design goal in the development of the .NET Framework to restrict the number of delegates used for event handling. A little more explanation is in order.

The first motivation for minimizing the number of delegate types has to do with a more efficient utilization of memory used by an application. Loading in more types means using up more memory. If every event defined by the classes within the Windows Forms framework were based on a custom delegate, hundreds of delegate types would have to be loaded into memory every time you ran a Windows Forms application. The Windows Forms framework can provide much better memory utilization by relying on a small handful of delegate types for the hundreds of events defined within the Form class and the various control classes.

A second motivation for minimizing the number of delegate types has to do with increasing the potential to achieve polymorphism with pluggable handler methods. When you write a handler method with a calling signature that matches the EventHandler delegate, you can bind it to the majority of the events raised by a form and its controls.

Let's look at a few examples of writing generic event handlers. I'll begin with an example in which you want to respond to the TextChanged event of several textboxes on a form by changing the user's input to uppercase. There's no need to create a separate event handler for each control. Instead, you can create a single event handler and bind it to the TextChanged event of several different textboxes (see Figure 3).

Figure 3 One Event Handler for Several Controls

Public Class MyApp : Inherits Form
  Friend WithEvents TextBox1 As TextBox
  Friend WithEvents TextBox2 As TextBox
  Friend WithEvents TextBox3 As TextBox
  '*** create event handler bound to several TextChanged events
  Private Sub TextChangedHandler(ByVal sender As System.Object, _
                                 ByVal e As System.EventArgs) _
                                 Handles TextBox1.TextChanged, _
                                         TextBox2.TextChanged, _
                                         TextBox3.TextChanged
    '*** convert sender to TextBox
    Dim txt As TextBox = CType(sender, TextBox)
    txt.Text = txt.Text.ToUpper()
  End Sub
End Class

The first thing you should note about this example is that a Handles clause isn't limited to a single event. You can include as many events as you'd like by using a comma-delimited list after the Handles keyword. In this example, the TextChangedHandler method is used to create three different event handlers. Therefore, this method is going to execute whenever the user changes the text in any of the three textboxes.

When the TextChangedHandler method executes, how do you know which TextBox object is raising the event? That's what the sender parameter is for. Remember that the sender parameter is passed in terms of the generic type Object. That means you must convert it to a more specific type before you can program against it. In the previous example, the sender parameter must be converted to a TextBox in order to access its Text property.

If you have experience building form-based applications with earlier versions of Visual Basic, you might be accustomed to using control arrays. A primary advantage to using control arrays in Visual Basic 6.0 is that this feature makes it possible to create a single handler method that responds to events raised by several different controls. Visual Basic .NET does not support control arrays. However, you should not be overly alarmed because, as you've just seen, Visual Basic .NET provides an alternate technique for binding a single handler method to several different events.

The event architecture of the .NET Framework also provides you with the ability to do things that have never been possible with control arrays. For example, you can create a single handler method to respond to events raised by several different types of controls. An example of a handler method that's bound to three different events on three different control types is shown in Figure 4.

Figure 4 Handler Method Bound to Different Events

Public Class MyApp : Inherits Form
  Friend WithEvents TextBox1 As TextBox
  Friend WithEvents CheckBox1 As CheckBox
  Friend WithEvents ListBox1 As ListBox
  '*** define form-wide dirty flag  
  Friend DirtyFlag As Boolean
  '*** set dirty flag when various events are raised
  Private Sub DirtyFlagHandler(ByVal sender As Object, _
                               ByVal e As EventArgs) _
              Handles TextBox1.TextChanged, _
                      CheckBox1.CheckedChanged, _
                      ListBox1.SelectedIndexChanged
    '*** set form-wide dirty flag
    DirtyFlag = True
  End Sub
End Class

As you can see, the scheme for binding handler methods to events is pretty flexible. The only requirement is that a handler method and the events it's bound to are based on the same delegate type. The fact that so many events in the .NET Framework are based on the EventHandler delegate type makes it easy to write generic handler methods.

When you write a generic handler method, it's sometimes necessary to write code to perform conditional operations that are only executed when the event source is a certain type of object. For example, your handler method can inspect the sender parameter using the TypeOf operator. This allows your handler method to execute one set of operations if the event source is a Button object and another set of operations if it's a CheckBox object, like this:

Sub GenericHandler1(sender As Object, e As EventArgs)
  If (TypeOf sender Is Button) Then
    Dim btn As Button = CType(sender, Button)
    '*** program against btn
  ElseIf (TypeOf sender Is CheckBox) Then
    Dim chk As CheckBox = CType(sender, CheckBox)
    '*** program against chk
  End If
End Sub

Customized Event Parameters

An event notification based on the EventHandler delegate doesn't typically send any meaningful information in the e parameter. The e parameter is often useless because it contains either a value of EventArgs.Empty or a value of Nothing. However, the designers of the .NET Framework created a convention for passing parameterized information from an event source to its event handlers. The convention involves the creation of a custom event argument class and a custom delegate type.

The mouse events raised by the Form class provide a good example of how this convention should be used. Parameterized information about the mouse position and about which mouse button has been pressed are modeled in a class named MouseEventArgs. The MouseEventArgs class contains an X and a Y property to track the mouse position as well as a Button property to indicate which mouse button has been pressed. Note that by convention the MouseEventArgs class must inherit from the generic class EventArgs.

The convention for passing parameterized information in an event notification requires a custom delegate to complement the custom event argument class. Therefore, there is a delegate named MouseEventHandler to complement the class MouseEventArgs. The handler delegate has this definition:

Delegate Sub MouseEventHandler(sender As Object, e As MouseEventArgs)

Now let's say you'd like to respond to a mouse-related event such as the MouseDown event of the Form class. You can write a handler method that looks like the one shown in Figure 5.

Figure 5 MouseEventHandler

Private Sub Form1_MouseDown(ByVal sender As Object, _
                            ByVal e As MouseEventArgs) _
                            Handles MyBase.MouseDown
  '*** capture mouse position
  Dim x_position As Integer = e.X
  Dim y_position As Integer = e.Y

  '*** take action depending on which button was pressed
  Select Case e.Button
    Case MouseButtons.Left
      '*** do something
    Case MouseButtons.Right
      '*** do something else
  End Select

End Sub

Note that the e parameter is very useful in the implementation of this handler method. The e parameter is used to determine the mouse position as well as to determine which mouse button has been pressed. All this parameterized information has been made possible by the design of the MouseEventArgs class.

You can find other examples of this parameterization convention used in the Windows Forms framework. For example, there is a class named KeyPressEventArgs that is complemented by a delegated type named KeyPressEventHandler. In addition, the ItemChangedArgs class is complemented by a delegate type named ItemChangedHandler. You will likely encounter other events with parameterized information that follow this same convention.

Parameterizing Custom Events

As an exercise, let's design a custom event to follow this convention for parameterization. I am going to use an example similar to what I used in my last few columns involving a BankAccount class. Consider the following code snippet:

Class BankAccount
  Sub Withdraw(ByVal Amount As Decimal)
    '*** send notifications if required
    If (Amount > 5000) Then
      '*** raise event
    End If
    '*** perform withdrawal
  End Sub
End Class

Assume you are required to raise an event whenever a BankAccount object experiences a withdrawal for an amount greater than $5,000. When you raise this event, you are required to pass all the registered event handlers the amount of the withdrawal as a parameter. First, you should create a new event argument class that inherits from the EventArgs class:

Public Class LargeWithdrawArgs : Inherits EventArgs
    Public Amount As Decimal
    Sub New(ByVal Amount As Decimal)
        Me.Amount = Amount
    End Sub
End Class

A custom event argument class should be designed to contain a public field for each parameterized value an event source needs to pass to its event handler. In this case, the LargeWithdrawArgs class has been designed with a Decimal field named Amount. Next, you must create a new delegate type to complement your new event argument class:

Delegate Sub LargeWithdrawHandler(ByVal sender As Object, _
                                  ByVal e As LargeWithdrawArgs)

By convention, this delegate type has been defined with an Object parameter named sender as the first parameter. The second parameter, e, is based on the custom event argument class.

Now that you have created the custom event argument class and a complementary delegate type, you can put them to use. Examine the following class definition:

Class BankAccount
  Public Event LargeWithdraw As LargeWithdrawHandler
  Sub Withdraw(ByVal Amount As Decimal)
    '*** send notifications if required
    If (Amount > 5000) Then
      Dim args As New LargeWithdrawArgs(Amount)
      RaiseEvent LargeWithdraw(Me, args)
    End If
    '*** perform withdrawal
  End Sub
End Class

The LargeWithdraw event has been modified to use the standard convention in the .NET Framework for passing parameterized information in an event notification. When it's time to raise a LargeWithdraw event in the Withdraw method, it's necessary to create a new instance of the LargeWithdrawArgs class and pass it as a parameter. Since the BankAccount object is the one that is raising the event, the Me keyword can be used to pass the sender parameter, as shown here:

Dim args As New LargeWithdrawArgs(Amount)
RaiseEvent LargeWithdraw(Me, args)

Now that you have seen how to create the event source, let's turn our attention to creating a handler method for this event. A handler method will be able to retrieve the parameterized information it needs through the e parameter. In this case, a handler method will use the e parameter to retrieve the value of the Amount field:

Sub Handler1(sender As Object, e As LargeWithdrawArgs)
  '*** retrieve parameterized information
   Dim Amount As Decimal = e.Amount
End Sub

Figure 6 shows a complete app in which a BankAccount object sends out event notifications when a large withdrawal is made. Note that this app follows the standard common language runtime convention for passing parameterized information in an event.

Figure 6 Custom Parameterized Events

'*** custom event arguments class
Class LargeWithdrawArgs : Inherits EventArgs
  Public Amount As Decimal
  Sub New(ByVal Amount As Decimal)
    Me.Amount = Amount
  End Sub
End Class

'*** delegate to complement custom event arguments class
Delegate Sub LargeWithdrawHandler(ByVal sender As Object, _
                                  ByVal e As LargeWithdrawArgs)

Class BankAccount
  Public Event LargeWithdraw As LargeWithdrawHandler
  Sub Withdraw(ByVal Amount As Decimal)
    '*** send notifications if required
    If (Amount > 5000) Then
      Dim args As New LargeWithdrawArgs(Amount)
      RaiseEvent LargeWithdraw(Me, args)
    End If
    '*** perform withdrawal
  End Sub
End Class

Class AccountAuditor
  Private WithEvents account As BankAccount
  Sub Handler1(ByVal sender As Object, _
               ByVal e As LargeWithdrawArgs) _
               Handles account.LargeWithdraw
    '*** retrieve parameterized information
    Dim Amount As Decimal = e.Amount
  End Sub
  Sub New(ByVal SourceAccount As BankAccount)
    Me.account = SourceAccount ''*** triggers binding of event handler
  End Sub
End Class

Module MyApp
  Sub Main()
    '*** create bank account object
    Dim account1 As New BankAccount()
    '*** register event handlers
    Dim listener1 As New AccountAuditor(account1)
    '*** do something that triggers callback
    account1.Withdraw(5001)
  End Sub
End Module

Wrap-up

This concludes my series on the fundamentals of programming with events using Visual Basic .NET. The first two columns showed the mechanics of raising and handling events. This month I concentrated on practical examples of programming with common events and delgates defined within the .NET Framework.

It is likely that the majority of events you will handle with Visual Basic .NET will be based on the EventHandler delegate. You have seen that it's possible to wire up several events to a single handler method. In cases like this, it's important that you know when and how to utilize the sender parameter. You have also seen other events that pass parameterized information using a custom argument class. All in all, you should now be prepared to work with an event-driven framework such as Windows Forms or ASP.NET.

Send your questions and comments for Ted to instinct@microsoft.com.

Ted Pattisonis an instructor and course writer at DevelopMentor (https://www.develop.com). He has written several books about Visual Basic and COM and is currently writing a book titled Building Applications and Components with Visual Basic .NET to be published by Addison-Wesley in 2003.