Put VB.NET events in the hands of AddHandler

This technical tip for intermediate VB.NET developers offers a look back at the AddHandler feature and how it addresses scenarios when there is no object variable to manipulate.

A simple conundrum posed by Visual Basic 6 -- how to trap and handle events from objects that are not assigned to an individual variable -- was solved by the earliest versions of VB.NET. The solution is the AddHandler command.

.NET provides the flexible handles clause to assign routines to handle events from object variables declared with the WithEvents keyword. There are, however, two common cases where there is no object variable to manipulate -- first, where there is no object instance associated with the event, and second, where the object is stored in a collection or an array.

AddHandler addresses both of these needs, and, as a bonus, permits us to change the handler routine at runtime. It also makes the WithEvents keyword unnecessary. This tip describes an example that demonstrates both cases in the same form.

Case 1: No Object Instance to Handle

The first case occurs with either an event declared in a module or with a shared event in a class.

As an example, error logging functionality was built into a class called ErrLogger. All of Errlogger's members, including its lone LogWritten event, which gets raised whenever the log file is written to, are declared using the shared keyword. ErrLogger is shown in Listing 1.

Listing 1: ErrLogging Class

Imports System
Imports System.IO
Public Class ErrLogger

    Private Shared strFilePath As String = "C:\Myapp\bin"   ' File path for log file
    Public Shared Event _
        LogWritten(ByVal Sender As Object, ByVal e As ELLogWrittenEventArgs)

    'Append date/time to errMsg, write to log file, & raise the LogWritten Event
    Public Shared Sub WriteErrMsg(ByVal errMsg As String)
        Dim sR As New StreamWriter(strFilePath, True)
            Dim FormattedMsg = String.Format("{0:G}", Now())& " : " & Errmsg & vbCrLf
            sR.WriteLine(FormattedMsg) 
            'Raise the Logwritten event
            RaiseEvent LogWritten(Nothing, New ELLogWrittenEventArgs (FormattedMsg))
            sR.Close()
    End Sub
End Class

'Custom EventArgs Class for the LogWritten event
Public Class ELLogWrittenEventArgs
    Inherits System.EventArgs
    Public Msg As String    ' The message being written to the log
    Sub New(ByVal newMsg As String)
        Me.Msg = newMsg
    End Sub  
End Class

When using only shared members of a class, you don't have to instantiate the class. That's like having a global object, without having to track what happens to it. However, without an object instance, the handles clause can't be appended to a subroutine to handle an event.

Fortunately, Addhandler doesn't need an object instance -- only an event name and the address of a handler routine.

The syntax of Addhandler is this:
AddHandler event, AddressOf eventhandler.

Thus, in the form's load event handler, this line of code assigns the subroutine ErrorLogged as a handler to the ErrLogger class' LogWritten event:
AddHandler Errlogger.LogWritten, AddressOf ErrorLogged

Case 2: Handling Events from an Object within a Collection

AddHandler solves the problem of trapping and handling events raised by objects stored in a collection or array with relative ease. Use it to assign a handler to each object prior to adding it to the array or collection.

Example Description

Envision a collection containing one or more collections, each of which contains one or more elements. (The example code was developed for electronic signs, each of which contains a collection of one or more lines of text that are populated at regular intervals from an external data source. The example fits collections of tables with a variable number of chairs or classrooms with a variable number of students just as easily.)

The signs are represented as Group objects that contain collections of Line objects. The Group and Line objects are constructed dynamically (earlier in the application) from data in a database, so a count is not available at design time.

The data members of the Line and Group objects are displayed on a form using separate objects. Groups are displayed using groupDisplay objects that are stored in a collection owned by the form. Similarly, the Lines are displayed (and controlled) by lineDisplay objects, which are stored in a collection that is owned by their parent groupDisplay object.

The form's user can click on a lineDisplay's button to override the display. The goal of this tip is to show how the form can trap and handle the override events raised by individual lineDisplay objects.

The Form

An image of the form, configured for two groupDisplays with three lineDisplays each, is shown in Figure 1.

Figure 1: Form with Three LineDisplays within Two GroupDisplays

Each lineDisplay object has four constituent controls to display and control the text:

  • Two labels -- to show the its name and current value,
  • A textbox to enter an operator over-ride value, and
  • A button to start the over-ride process.

Note that there's no data from the external source in these controls -- but a user can override "nothing" just as easily as "something." Also note that the bottom half of the form displays errors that are logged by the ErrLogger object -- as described above.

Handling the Event

AddHandler is used to assign a handler to the object before it is added to the collection. The object that raised the event is identified by using the sender object in the event's parameters.

The extra layer of collections in the example means that there's an extra required step: the lineDisplay's override event notifies the groupDisplay, which, in turn raises its own override event to notify the form. Each layer has its own handler routine, and the notification bubbles up through each layer.

This is less complex than it sounds. The relevant parts of the lineDisplay and groupDisplay classes are shown in Listings 2 and 3. The "irrelevant" parts of these classes, which create and place constituent controls and do other bookkeeping, have been omitted for clarity.

Listing 2: lineDisplay Class

Public Class LineDisplay
 '... code to assign the object to parent group, create & place controls 
 ' as well as the object constructor(s) omitted for clarity.
Public Event Override(ByVal Sender As Object, ByVal e As EventArgs)
Dim WithEvents btn As New Button

    'Handle Click of the object's OverRide button 
Private Sub btnOverRide_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnOverRide.Click

        ' *** Raise Event to Inform Client Object - A Group Display Object **** 
        RaiseEvent Override(Me, New EventArgs)

      End Sub

End Class

Listing 3: groupDisplay Class

Public Class GroupDisplay
    ' Fire an event that delegates the LineDisplay Override event to this Object's Client (a Form Object)
    Public Event Override(ByVal sender As Object, ByVal e As GDOREventArgs)

     Sub New()
' Code to set the group associated with the display, assign reference to the panel for the group, 
' and to instantiate the groupbox for this group omitted for clarity.

        Dim Ln As Line
        For Each Ln In Me.MyGroup.Lines
            ' create a lineDisplay 
            ' **** Dynamically add the sub HandleLDButton as a handler ****
            ' add it to the collection of line displays for this groupdisplay
            Dim LD As LineDisplay = New LineDisplay(Me.Grp, Ln)
            AddHandler LD.Override, AddressOf HandleLDButton
            Me.LDisplays.Add(Ln, LD)
        Next
    End Sub

    ' Dynamically added as handler to each LDisplay.Override Event
    Sub HandleLDButton(ByVal sender As Object, ByVal e As EventArgs)
        ' Raise the event in a client form 
        Dim LD As LineDisplay = CType(sender, LineDisplay)
        RaiseEvent Override(Me, New GDOREventArgs(LD))
    End Sub

End Class

Public Class GDOREventArgs : Inherits EventArgs
    Public MyLD As LineDisplay
    Sub New(ByVal LD As LineDisplay)
        Me.MyLD = LD
    End Sub
End Class

As shown in Listing 2, lineDisplay's override event is raised in the button's click event handler. Meanwhile, the groupDisplay code in Listing 3 shows a lineDisplay object being created for each Line object in the group's Lines collection and the event handler -- (HandleLDButton()) -- being assigned.

When a particular lineDisplay raises its override event, it is handled by the groupDisplay via its HandleLDButton routine. To notify the client form, HandleLDButton raises the groupDisplay's override event.

How does the form know which object raised the event? That is in the lineDisplay's override event, as the sender parameter. That information is passed along when re-raising the event (for the client form) with a System.EventArgs-derived object that incorporates the lineDisplay object as a custom field.

The last piece is the code for the client form's load event, the relevant parts of which are shown in Listing 4.

Listing 4: Relevant Form Load Event Code

Dim gDisplays As Hashtable

Private Sub FormMain_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load

        'Make  Sub ErrorLogged Handle the LogWritten event of ErrLogger
        AddHandler Errlogger.LogWritten, AddressOf ErrorLogged

        'Create and set up group displays, assigning the event handler
        Dim Grp As Group
        For Each Grp In Main.Groups
            'create new groupDisplay for this group
            Dim GD As New GroupDisplay()
            'Add the event handler -bubbles up from lineDisplay to groupDisplay to here
            AddHandler GD.Override, AddressOf ButtonHandler
            gDisplays.Add(Grp, GD) ' add to the hashtable
        Next

End Sub
Private Sub ButtonHandler(ByVal sender As Object, ByVal e As GDOREventArgs)
'code here
End Sub

Note the assignment of the ErrLogger.LogWritten event that was excerpted earlier. After that, there's a simple for loop to create groupDisplay objects and assign the form's ButtonHandler routine as the groupDisplay object's override event's handler.

Conclusion

There are countless real-world applications dealing with events raised by objects stored in collections or non-instantiated class events. Where they occur, Addhandler addresses the inter-object communication problem very neatly.

Bruce D. Neiger is a licensed professional engineer working for Parsons Transportation Group (PTG) in New York City. His 20-year career has included of highway-related air quality and noise modeling, application development, OOP, and object architecture. He can be reached at Bruce.D.Neiger@Parsons.com.

This was first published in February 2007

Dig deeper on VB 6 to VB .NET Migration

0 comments

Oldest 

Forgot Password?

No problem! Submit your e-mail address below. We'll send you an email containing your password.

Your password has been sent to:

-ADS BY GOOGLE

SearchCloudComputing

SearchSoftwareQuality

SearchSOA

TheServerSide

SearchCloudApplications

Close