Skip to content

6. Event Handling

In the previous chapter, we discussed the concept of events associated with components. We will now see how to create events in our own classes. This is a difficult concept that will not be of interest to beginner developers, who are advised to skip directly to the next chapter.

6.1. Delegate objects

The statement

    Delegate Function operation(ByVal n1 As Integer, ByVal n2 As Integer) As Integer

defines a type called operation, which is actually a function prototype that accepts two integers and returns an integer. It is the delegate keyword that makes operation a function prototype definition. The [operation] type can be assigned to a variable as follows:


        Dim op As New operation(AddressOf [function])

A variable of type [delegate] is constructed as an object. The constructor parameter is the function associated with the delegate instance. In VB, this function is specified as AddressOf function, where [function] is the name of the function. Once a delegate instance is created, it can be used as a function:


        ' execute the delegate
        Dim n As Integer = op(4, 7)

The delegate [operation] was defined to take two parameters of type [integer] and return a result of the same type. Therefore, its instance [op] above is used as such a function. Consider the following example:


' delegate functions
Imports System

Public Class delegate1
    ' definition of a function prototype
    ' accepts 2 integers as parameters and returns an integer
    Delegate Function operation(ByVal n1 As Integer, ByVal n2 As Integer) As Integer

    ' two instance methods corresponding to the prototype
    ' add
    Public Function add(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
        Console.Out.WriteLine(("add(" & n1 & "," & n2 & ")"))
        Return n1 + n2
    End Function

    'subtract
    Public Function subtract(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
        Console.Out.WriteLine(("subtract(" & n1 & "," & n2 & ")"))
        Return n1 - n2
    End Function

    ' a static method corresponding to the prototype
    Public Shared Function increase(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
        Console.Out.WriteLine(("increase(" & n1 & "," & n2 & ")"))
        Return n1 + 2 * n2
    End Function

    ' test program
    Public Shared Sub Main()
        ' define an operation object to store functions
        ' register the static function increase
        Dim op As New operation(AddressOf delegate1.increase)
        ' execute the delegate
        Dim n As Integer = op(4, 7)
        Console.Out.WriteLine(("n=" & n))
        ' Create an object c1 of type class1
        Dim c1 As New delegate1
        ' Store the Add method of c1 in the delegate
        op = New delegate1.operation(AddressOf c1.add)
        ' executing the delegate object
        n = op(2, 3)
        Console.Out.WriteLine(("n=" & n))
        ' Register the 'subtract' method of c1 in the delegate
        op = New delegate1.operation(AddressOf c1.subtract)
        n = op(2, 3)
        Console.Out.WriteLine(("n=" & n))
    End Sub
End Class

The results of the execution are as follows:

increase(4,7)
n=18
add(2,3)
n=5
subtract(2,3)
n=-1

6.2. Event handling

What can a delegate object be used for?

As we will see in the following example, it is primarily used for event handling. A class C1 can generate evti events. When an evti event occurs, an object of type C1 will trigger the execution of an evtiDéclenché object of type delegate. All functions registered in the evtiDéclenché delegate object will then be executed. If an object C2 using an object C1 wants to be notified of the occurrence of the evti event on object C1, it will register one of its methods, C2.f, with the delegate object C1.evtiDéclenché of object C1 so that the method C2.f is executed every time the evti event occurs on object C1. Since the delegate object C1.evtiDéclenché can register multiple functions, different objects Ci can register with the delegate C1.evtiDéclenché to be notified of the event evti on C1.

6.2.1. Declaring an Event

An event is declared as follows (1):


Delegate Sub proc(argument list)
Public Event event as proc

or (2)


Public Event event(argument list)

The keyword [Event] defines a piece of data as an event. The event is associated with the signature that its handlers must have. This signature can be defined using a delegate (method 1) or directly (method 2).

6.2.2. Defining Event Handlers

When an event is triggered by the [RaiseEvent] statement, it must be handled. The procedures responsible for handling events are called event handlers. When creating an [Event] object, only the signature of the event handlers is declared. This signature is that of the [delegate] object associated with the event. Once this is done, handlers can be associated with the event. These are procedures that must have the same signature as the [delegate] associated with the event. To associate an event handler with an event, use the [AddHandler] statement with the following syntax:

AddHandler event, AddressOf eventhandler
  • event: event variable to which a new event handler is associated
  • eventhandler: name of the event handler—must have the same signature as the [delegate] associated with the event

You can associate as many handlers as you want with an event. They will all be executed when the event they are associated with is triggered.

6.2.3. Triggering an Event

To trigger an event programmatically, use the [RaiseEvent] statement:

RaiseEvent eventname[( argumentlist )]
  • eventname: event variable (declared with the Event keyword)
  • argumentlist: arguments of the delegate associated with the event.

All handlers that have been associated with the event using the AddHandler statement will be called. They will receive as parameters the parameters defined in the event signature.

6.2.4. An example

Consider the following example:


'event handling
Imports System

Public Class myEventArgs
    Inherits EventArgs
    ' the class of an event
    ' attribute
    Private _input As String

    ' constructor
    Public Sub New(ByVal input As String)
        _input = input
    End Sub

    ' read-only input property
    Public Overrides Function ToString() As String
        Return _input
    End Function
End Class

Public Class emitter
    ' the class that emits an event
    ' attribute
    Private _name As String    ' name of the emitter

    ' constructor
    Public Sub New(ByVal name As String)
        _name = name
    End Sub

    ' ToString
    Public Overrides Function ToString() As String
        Return _name
    End Function

    ' the prototype of the functions responsible for handling the event
    Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)

    ' the pool of event handlers
    Public Event evtHandler As _evtHandler

    ' Method to request the emission of an event
    Public Sub sendEvent(ByVal evt As myEventArgs)
        ' Notify all subscribers
        ' act as if the event originated here
        RaiseEvent evtHandler(Me, evt)
    End Sub
End Class

' an event handler class
Public Class Subscriber
    ' attribute
    Private name As String    ' subscriber name
    Private sender As EventSender    ' the event sender

    ' constructor
    Public Sub New(ByVal name As String, ByVal e As sender)
        ' store the subscriber's name
        Me.name = name
        ' and the event sender
        Me.sender = e
        ' subscribe to receive events from sender e
        AddHandler e.evtHandler, AddressOf eventHandler
    End Sub

    ' event handler
    Public Sub processEvent(ByVal sender As Object, ByVal event As myEventArgs)
        ' Display event
        Console.Out.WriteLine(("The object [" + sender.ToString + "] reported the invalid input [" + evt.ToString + "] to the subscriber [" + name + "]"))
    End Sub
End Class

' a test program
Public Class test
    Public Shared Sub Main()
        ' creating an event emitter
        Dim emitter1 As New emitter("emitter1")
        ' Create an array of subscribers
        ' for events emitted by emitter1
        Dim subscribers() As Subscriber = {New Subscriber("s1", emitter1), New Subscriber("s2", emitter1)}
        ' Read a sequence of integers from the keyboard
        ' as soon as one is incorrect, an event is emitted
        Console.Out.Write("Integer (press Enter to stop): ")
        Dim input As String = Console.In.ReadLine().Trim()
        ' as long as the entered line is not empty
        While input <> ""
            ' Is the input an integer?
            Try
                Dim n As Integer = Integer.Parse(input)
            Catch
                ' it is not an integer
                ' Notify everyone
                emitter1.sendEvent(New myEventArgs(input))
            End Try
            ' notify everyone
            ' new input
            Console.Out.Write("Integer (press any key to stop): ")
            input = Console.In.ReadLine().Trim()
        End While
        ' end
        Environment.Exit(0)
    End Sub
End Class

The code above is quite complex. Let’s break it down. In event handling, there is an event sender that sends event details (EventArgs) to subscribers who have expressed interest in the events in question. The event-sending class here is as follows:


Public Class Sender
    ' the class that emits an event
    ' attribute
    Private _name As String    ' sender name

    ' constructor
    Public Sub New(ByVal name As String)
        _name = name
    End Sub

    ' ToString
    Public Overrides Function ToString() As String
        Return _name
    End Function

    ' the prototype of the functions responsible for handling the event
    Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)

    ' the pool of event handlers
    Public Event evtHandler As _evtHandler

    ' Method to request the dispatch of an event
    Public Sub sendEvent(ByVal evt As myEventArgs)
        ' Notify all subscribers
        ' act as if the event originated here
        RaiseEvent evtHandler(Me, evt)
    End Sub
End Class

Each emitter object has a name set by default. The ToString method has been redefined so that when an emitter object is converted to a string, its name is returned. The class defines an event:


        ' the prototype of the functions responsible for handling the event
        Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)

        ' the event
        Public Event evtHandler As _evtHandler

The first argument of an event handler function will be the object that emits the event. The second argument will be of type myEventArgs, an object that provides details about the event, which we will discuss later. To trigger an event from outside an emitter object, we add the envoyerEvt method to the class:


    ' method to request the emission of an event
    Public Sub sendEvt(ByVal evt As myEventArgs)
        ' notify all subscribers
        ' we act as if the event originated here
        RaiseEvent evtHandler(Me, evt)
    End Sub

We need to define the type of events triggered by the emitter class. To do this, we have defined the myEventArgs class:


Public Class myEventArgs
    ' the class of an event
    ' attribute
    Private _input As String

    ' constructor
    Public Sub New(ByVal input As String)
        _input = input
    End Sub

    ' read-only input property
    Public Overrides Function ToString() As String
        Return _input
    End Function
End Class

The events we are interested in are incorrect keyboard inputs. We will ask a user to type integers on the keyboard, and as soon as they type a string that does not represent an integer, we will trigger an event. As the event detail, we will simply provide the incorrect input. This is the purpose of the _input attribute of the class. A myEventArgs object is therefore created with the incorrect input as its parameter. We also override the ToString method so that when we convert a myEventArgs object to a string, we get the _input property.

We have defined the event emitter and the type of events it emits. Next, we define the type of subscribers interested in these events.


    ' an event handler class
    Public Class Subscriber
        ' attribute
        Private name As String     ' Subscriber name
        Private sender As EventSender     ' the event sender

        ' constructor
        Public Sub New(ByVal name As String, ByVal e As sender)
            ' store the subscriber's name
            Me.name = name
            ' and the event sender
            Me.sender = e
            ' subscribe to receive events from sender e
            AddHandler e.evtHandler, AddressOf eventHandler
        End Sub

        ' event handler
        Public Sub eventHandler(ByVal sender As Object, ByVal evt As myEventArgs)
            ' Display event
            Console.Out.WriteLine(("The object [" + sender.ToString + "] reported the invalid input [" + evt.ToString + "] to the subscriber [" + name + "]"))
        End Sub
    End Class

A subscriber is defined by two parameters: its name (name attribute) and the emitter object whose events it wants to handle (sender attribute). These two parameters are passed to the object's constructor. During this same construction, the subscriber subscribes to the emitter's events:


            ' we register to receive events from the sender e
            AddHandler e.evtHandler, AddressOf eventHandler

The function registered with the publisher is traitementEvt. This method of the subscriber class displays the two arguments it received (sender, evt) as well as the name of the receiver (nom). The types of the events produced, the type of the publisher of these events, and the types of the subscribers have been defined. All that remains is to implement them:


    ' a test program
    Public Class test
        Public Shared Sub Main()
            ' creating an event sender
            Dim sender1 As New sender("sender1")
            ' Create an array of subscribers
            ' for events emitted by emitter1
            Dim subscribers() As Subscriber = {New Subscriber("s1", emitter1), New Subscriber("s2", emitter1)}
            ' read a sequence of integers from the keyboard
            ' as soon as one is incorrect, an event is emitted
            Console.Out.Write("Integer (press Enter to stop): ")
            Dim input As String = Console.In.ReadLine().Trim()
            ' as long as the entered line is not empty
            While input <> ""
                ' Is the input an integer?
                Try
                    Dim n As Integer = Integer.Parse(input)
                Catch
                    ' it is not an integer
                    ' Notify everyone
                    emitter1.sendEvent(New myEventArgs(input))
                End Try
                ' notify everyone
                ' new input
                Console.Out.Write("Integer (press any key to stop): ")
                input = Console.In.ReadLine().Trim()
            End While
            ' end
            Environment.Exit(0)
        End Sub

We create an event emitter object:


        ' Create an event emitter
        Dim emitter1 As New emitter("emitter1")

We create an array of two subscribers for the events emitted by the emitter1 object:


        ' creating an array of subscribers
        ' for events emitted by emitter1
        Dim subscribers() As Subscriber = {New Subscriber("s1", sender1), New Subscriber("s2", sender1)}

We ask the user to enter integers via the keyboard. As soon as an incorrect entry is made, we ask issuer1 to send an event to its subscribers:


                ' we notify everyone
                emitter1.sendEvent(New myEventArgs(input))

The event sent is of type myEventArgs and contains the incorrect input. Both subscribers should receive this event and report it. This is shown in the following execution.

dos>evt1
Integer (nothing to stop): 4
Integer (nothing to stop): a
The object [emitter1] reported the invalid input [a] to the subscriber [s1]
The object [emitter1] reported the invalid input [a] to the subscriber [s2]
Integer (no stop): 1.6
The object [sender1] reported the incorrect entry [1.6] to the subscriber [s1]
The object [sender1] reported the invalid input [1.6] to the subscriber [s2]
Integer (nothing to stop):