Raising Events and Responding to Events

 

Mike Gunderloy
Lark Group, Inc.

February 2002

Summary: Discusses raising and responding to events in Visual Basic .NET, and how to utilize the flexibility Visual Basic .NET offers in associating event handlers with events, including dynamic association of event handlers with events at run time. (14 printed pages)

Objectives

  • Understand event processing in Microsoft® Visual Basic® .NET
  • Use Visual Basic .NET code to raise an event
  • Use Visual Basic .NET code to respond to an event

Assumptions

The following should be true for you to get the most out of this document:

  • You are familiar with Visual Basic programming
  • You understand the basic concept of event-driven programming
  • You have access to Visual Basic .NET

Contents

Event-Driven Programming
Practice Implementing and Responding to Events
What's New Since Visual Basic 6.0?
Summary

Event-Driven Programming

Before the advent of Microsoft® Windows®, many programs had a strictly procedural design. You ran the program, it did its work while you waited, and when it was done, the command prompt returned and you could run another program.

With Windows, a new programming paradigm became dominant: event-driven programming. As you interact with an application, events occur. For example, clicking a command button raises a button's Click event. Sections of code called event handlers react to events. You can think of events as signals from one part of your application to another. It's up to you as the developer to write code to respond to events. Events with no handler are simply discarded by Windows.

In this document, you'll learn how Visual Basic .NET enables you to handle both sides of the event contract. You can both raise events and respond to events with Visual Basic .NET. Visual Basic .NET also offers much more flexibility than did Visual Basic 6.0 in associating events with event handlers, including changing the association at run time.

Event Sources

An event source (sometimes called an event sender) is any object that can signal the occurrence of an event. Broadcasting this signal is called raising the event. Many of the classes in the .NET Framework Class Library, as well as the user interface components in the Windows Forms and Web Forms packages, can raise events. Figure 1 shows some of the events from the System.Windows.Forms.Control class, which are generally inherited by all of the controls on a Windows Form.

Figure 1. Events of the System.Windows.Forms.Control class

Visual Basic .NET lets you create your own event sources. You need to take two steps to implement your own event in a class or interface:

  1. Declare the event signature using the Event keyword.
  2. Raise the event using the RaiseEvent statement.

To declare an event, use the Event keyword in the declarations section of a class module (or of an interface). Events are always declared as public. An event can have an argument list, but it cannot have optional arguments, ParamArray arguments, or a return value. For example, you might declare an event named ConnectFailed this way:

Public Event ConnectFailed(ByVal Reason As Integer)

To raise the ConnectFailed event, you'd use the RaiseEvent keyword somewhere in the class:

RaiseEvent ConnectFailed(1)

The argument list of an event can be used for two-way communication between the code that raises the event and the code that responds to the event. In particular, you can implement a Boolean argument, often named Cancel, which the event handler can use to tell the code raising the event to stop whatever it is doing. Such an event might be declared like this:

Public Event ShutDown (ByRef Cancel As Boolean)

Note that the Cancel argument must be passed by reference so that the code raising the event can respond to the value set by the event handler.

Event Receivers

An event receiver, often called an event handler is a subroutine that responds to an event. (Event handlers cannot be functions, because an event cannot have a return value). Typically an event handler is named with the name of an object plus the name of an event, although this is not required.

For example, suppose you had declared the ConnectFailed event in a class named Customer, and you had declared an instance of the Customer class named CurrentCustomer. An event handler for the ConnectFailed event could be declared like this:

Protected Sub CurrentCustomer_ConnectFailed( _
 ByVal Reason As Integer)

Hooking Events to Event Handlers

To tell Visual Basic .NET that a particular event handler should handle a particular event from a particular object, you need to associate the event handler with the event. There are two ways to do this. First, you can use the WithEvents statement and the Handles clause to set up the association at compile time. Second, you can use the AddHandler statement to set up the association at run time.

To handle the events from an object, you declare that object using the WithEvents keyword, as in this example:

Private WithEvents CurrentCustomer As Customer

To associate a particular event handler with an event on this class, you declare the event handler using the Handles clause:

Protected Sub CurrentCustomer_ConnectFailed(_
 ByVal Reason As Integer) _
 Handles CurrentCustomer.ConnectFailed

Declaring an event handler in this way fixes the association between the event and the handler at compile time. You can also use the more flexible AddHandler statement to make the association between an event and an event handler at run time, on the fly:

Protected Sub CurrentCustomer_ConnectFailed(_
 ByVal Reason As Integer)
…
AddHandler CurrentCustomer.ConnectFailed, _
 AddressOf CurrentCustomer_ConnectFailed

If you use AddHandler to hook up an event, the event procedure should not have a Handles clause. There is also a RemoveHandler statement that breaks the association between an event and an event handler:

RemoveHandler CurrentCustomer.ConnectFailed, _
 AddressOf CurrentCustomer_ConnectFailed

Handling Events from COM Sources

If your Visual Basic .NET code is interoperating with legacy COM components, you are free to handle events from the COM components within .NET code. To do so, you must obtain or generate a runtime callable wrapper (RCW) by one of these means:

  1. Obtain a primary interop assembly from the vendor of the COM component.
  2. Use the TLBIMP command line tool to create a RCW for the COM component.
  3. Set a reference from your .NET project to the COM component directly from your Visual Basic .NET project.

Regardless of how you create the RCW, it will make all of the events of the COM class available to your .NET code. You can use Handles or AddHandler to associate these events with handlers, just as you can with events raised in managed code.

Note For more information on RCWs and COM interop, refer to Calling COM Components from .NET Clients.

Events and Delegates

The connection between events and event handlers is actually implemented by special objects called delegates. A delegate is an object that you can use to call a method of another object. Delegates are similar to function pointers in languages such as C++. However, delegates specify the type of objects that they can work with, and so avoid the management problems inherent in pointing to arbitrary functions. Because of this, delegates are sometimes called type-safe function pointers.

Whenever you declare an event with the Event keyword, Visual Basic .NET implicitly creates a delegate class for you with the name EventNameEventHandler. For example, declaring a ConnectFailed event automatically creates a delegate named ConnectFailedEventHandler.

If you like, you can use the delegate object explicitly with the AddHandler statement. You will normally have to fully qualify the name of the delegate to do so. For example, if the ConnectFailed event is a member of the Customer class in a project named Sales, these two statements are equivalent:

AddHandler CurrentCustomer.ConnectFailed, _
    AddressOf CurrentCustomer_ConnectFailed
AddHandler CurrentCustomer.ConnectFailed, _
New Sales.Customer.ConnectFailedEventHandler( _
AddressOf CurrentCustomer_ConnectFailed)

There's no difference in implementation or performance between these two forms of the AddHandler statement, as you can verify by using ILDASM to examine the generated code. In almost all cases, you should use the shorter form and let Visual Basic .NET handle the delegate objects implicitly. The one exception to this is that you may occasionally wish to share a single delegate type between multiple events, which you can do by declaring an event to use an explicit delegate type:

Public Event EventName As DelegateType

In this case, you'll want to use the longer form of the AddHandler statement to ensure that Visual Basic .NET uses the correct delegate type.

Practice Implementing and Responding to Events

In the following example, you'll create a class library containing a class that raises a pair of events. Then you'll create a user interface application that can respond to those events. You'll use both compile time event association with Handles and run-time event association with AddHandler.

Create the Class Library

Follow these steps to create the class library that will raise the events:

  1. Open Microsoft® Visual Studio® .Net, and on the Start Page, click New Project.

  2. On the tree view on the left-hand side of the screen, click Visual Basic Projects.

  3. Click Class Library to select it as the project template.

  4. Set the name of the application to Water and click OK to create the project.

  5. Click the class called Class1.vb in the Solution Explorer window and rename it to Bucket.vb.

  6. Select the code for Class1 in Bucket.vb (this will be an empty class definition) and replace it with the following code:

    Public Class Bucket
    
        Private ContentsValue As Integer
        Private CapacityValue As Integer
        Private NameValue As String
    
        Public Event Full()
        Public Event Overflowing(_ 
         ByVal sender As System.Object)
    
        Public Sub New(ByVal Capacity As Integer)
            mintCapacity = Capacity
            mintContents = 0
        End Sub
    
        Public Sub Empty()
            mintContents = 0
        End Sub
    
        Public ReadOnly Property Contents()
            Get
                Contents = mintContents
            End Get
        End Property
    
        Public Property Name() As String
            Get
                Name = mstrName
            End Get
            Set(ByVal Value As String)
                mstrName = Value
            End Set
        End Property
    
        Public Sub Add(ByVal Amount As Integer)
            mintContents = mintContents + Amount
            If mintContents > mintCapacity Then
                RaiseEvent Overflowing(Me)
                mintContents = mintCapacity
            ElseIf mintContents = mintCapacity Then
                RaiseEvent Full()
            End If
        End Sub
    
    End Class
    

    This code creates a class named Bucket to represent a bucket that can be filled with water. The New method initializes the bucket with a specified capacity. The Name property assigns a name to the bucket. The Add method adds water to the bucket, and the Empty method empties the bucket.

    The Bucket class includes two event declarations:

        Public Event Full()
        Public Event Overflowing(_ 
         ByVal sender As System.Object)
    

    These two events are both raised within the Add method. If the amount of water in the bucket exceeds the capacity of the bucket, the code raises the Overflowing event. Otherwise, if the amount of water in the bucket exactly equals the capacity of the bucket, the code raises the Full event.

    Note that the argument lists of the two events are different. You have complete flexibility to pass whatever information you need in any particular event.

  7. On the Build menu, click Build or press Ctrl+Shift+B to build the class library.

Create the User Interface Project

Follow these steps to create a Windows Application to test the events of the Bucket class:

  1. Open Visual Studio .Net and on the Start Page, click New Project.

  2. On the tree view on the left-hand side of the screen, click Visual Basic Projects.

  3. Click Windows Application to set it as the project template.

  4. Set the name of the application to BucketTest and click OK to create the project.

  5. Select the form called Form1.vb in the Solution Explorer window and rename it to frmBuckets.vb.

  6. Create the form shown in Figure 2 by adding the appropriate controls and setting the properties of those controls, as outlined in Table 1.

    Table 1. Controls for frmDragDrop.vb

    Control Type Property Value
    Form Text Buckets
    Label Text Bucket 1
    Button Name btnAdd1
      Text Add
    ProgressBar Name pb1
      Maximum 10
    Label Name lblFull1
      Text FULL
      Visible False
    Label Text Bucket 2
    Button Name btnAdd2
      Text Add
    ProgressBar Name pb2
      Maximum 10
    Label Name lblFull2
      Text FULL
      Visible False
    Label Text Bucket 3
    Button Name btnAdd3
      Text Add
    ProgressBar Name pb3
      Maximum 10
    Label Name lblFull3
      Text FULL
      Visible False
    ListBox Name lboOverflow

    Figure 2. Form for testing the Bucket class events

Add Code to Handle Events

Now you're ready to write code to handle the events from the Bucket class. This code will create three objects of the Bucket class. The Full events will be individually connected to event handlers at compile time using Handles. The Overflow events will all be handled by a single procedure, dynamically associated with the events at run time by using AddHandler.

On the View menu, click Code, and enter this code before the Windows Form Designer generated code:

    Dim WithEvents Bucket1 As New Water.Bucket(10)
    Dim WithEvents Bucket2 As New Water.Bucket(10)
    Dim WithEvents Bucket3 As New Water.Bucket(10)

Now enter this code after the Windows Form Designer generated code:

    Private Sub Form1_Load(_ 
     ByVal sender As System.Object, _
     ByVal e As System.EventArgs) Handles MyBase.Load
        Bucket1.Name = "Bucket 1"
        Bucket2.Name = "Bucket 2"
        Bucket3.Name = "Bucket 3"
        AddHandler Bucket1.Overflowing, _
         AddressOf HandleOverflow
        AddHandler Bucket2.Overflowing, _
         AddressOf HandleOverflow
        AddHandler Bucket3.Overflowing, _
         AddressOf HandleOverflow
    End Sub

    Private Sub btnAdd1_Click(_ 
     ByVal sender As System.Object, _
     ByVal e As System.EventArgs) Handles btnAdd1.Click
        Bucket1.Add(1)
        pb1.Value = Bucket1.Contents
    End Sub

    Private Sub btnAdd2_Click(_
     ByVal sender As System.Object, _
     ByVal e As System.EventArgs) Handles btnAdd2.Click
        Bucket2.Add(1)
        pb2.Value = Bucket2.Contents
    End Sub

    Private Sub btnAdd3_Click(_ 
     ByVal sender As System.Object, _
     ByVal e As System.EventArgs) Handles btnAdd3.Click
        Bucket3.Add(1)
        pb3.Value = Bucket3.Contents
    End Sub

    Private Sub Bucket1_Full() Handles Bucket1.Full
        lblFull1.Visible = True
    End Sub

    Private Sub Bucket2_Full() Handles Bucket2.Full
        lblFull2.Visible = True
    End Sub

    Private Sub Bucket3_Full() Handles Bucket3.Full
        lblFull3.Visible = True
    End Sub

    Private Sub HandleOverflow(_
     ByVal sender As System.Object)
        lboOverflow.Items.Add(sender.Name & " Overflow!")
    End Sub

Here's what you'll find in this code:

  • The code in the Form Load assigns values to the Name property of the three Bucket objects. It also calls AddHandler to direct all of the Overflowing events to the HandleOverflow procedure. As you can see, a single event handler can handle multiple events, as long as all of the events have compatible argument lists.
  • The Add procedures call the Add methods of their respective Bucket objects, and add one unit of water to the bucket when they are called. They also update the ProgressBar controls to display the state of the buckets on screen.
  • The Full procedures are the event handlers for the Full events. These procedures make the corresponding "Full" labels visible on the user interface.
  • The HandleOverflow event procedure handles all of the Overflowing events. It uses the passed-in reference to the object that posted the event to build a message for the list box on the form.

Try It Out

To see the drag and drop in action, follow these steps:

  1. Press F5 to start the project.

  2. Click the first Add button twice. Notice that the progress bar control is automatically updated.

  3. Click the second Add button ten times. On the tenth click the "Full" label will appear next to the progress bar.

  4. Click the third Add button eleven times. On the tenth click the "Full" label will appear next to the progress bar. On the eleventh click, a message will appear in the list box.

  5. Click the second Add button once more. Another message will appear in the list box.

    Figure 3 shows the user interface at this point.

    Figure 3. The Buckets test form in action

What's New Since Visual Basic 6.0?

While the basic concept of event-driven programming hasn't changed in Visual Basic .NET, some implementation details are different. The largest change is the addition of the AddHandler and RemoveHandler statements, to allow dynamic association of event handlers with events at run time.

Summary

Visual Basic .NET makes it easy both to implement new events and to respond to events. Using just a handful of keywords (Event, RaiseEvent, Handles, AddHandler, and RemoveHandler) you can create, handle, and associate all the events that your application requires. And because Visual Basic .NET, like the other .NET languages, is built on top of the services provided by the common language runtime, you're free to mix languages when writing event-driven code. An event handler written in Visual Basic .NET can handle an event declared in a C# library or vice versa.

About the Author

Mike Gunderloy writes about software and raises chickens in eastern Washington State. He's the co-author of Access 2002 Developer's Handbook and author of SQL Server Developer's Guide to OLAP with Analysis Services, both from Sybex. He's been writing code for Microsoft products since the prehistoric pre-Windows era, and has no intention of stopping any time soon. Reach him at MikeG1@larkfarm.com

About Informant Communications Group

Informant Communications Group, Inc. (www.informant.com) is a diversified media company focused on the information technology sector. Specializing in software development publications, conferences, catalog publishing and Web sites, ICG was founded in 1990. With offices in the United States and the United Kingdom, ICG has served as a respected media and marketing content integrator, satisfying the burgeoning appetite of IT professionals for quality technical information. http://www.informant.com

Copyright © 2002 Informant Communications Group and Microsoft Corporation

Technical editing: PDSA, Inc. or KNG Consulting, Inc.