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 opération(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 opération(AddressOf [fonction])

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:


        ' the delegate is executed
        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:


' delegated functions
Imports System

Public Class delegate1
    ' function prototype definition
    ' accepts 2 integers as parameters and returns an integer
    Delegate Function opération(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
 
    ' two instance methods corresponding to the prototype
    ' add
    Public Function ajouter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
        Console.Out.WriteLine(("ajouter(" & n1 & "," & n2 & ")"))
        Return n1 + n2
    End Function
 
    ' subtract
    Public Function soustraire(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
        Console.Out.WriteLine(("soustraire(" & n1 & "," & n2 & ")"))
        Return n1 - n2
    End Function
 
    ' a static method corresponding to the prototype
    Public Shared Function augmenter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
        Console.Out.WriteLine(("augmenter(" & n1 & "," & n2 & ")"))
        Return n1 + 2 * n2
    End Function
 
    ' test program
    Public Shared Sub Main()
        ' define an operation object to store functions
        ' we register the static function increase
        Dim op As New opération(AddressOf delegate1.augmenter)
        ' the delegate is executed
        Dim n As Integer = op(4, 7)
        Console.Out.WriteLine(("n=" & n))
        ' creation of a c1 object of type class1
        Dim c1 As New delegate1
        ' we register c1's add method in the delegate
        op = New delegate1.opération(AddressOf c1.ajouter)
        ' execution of delegated object
        n = op(2, 3)
        Console.Out.WriteLine(("n=" & n))
        ' we register the subtract method of c1 in the delegate
        op = New delegate1.opération(AddressOf c1.soustraire)
        n = op(2, 3)
        Console.Out.WriteLine(("n=" & n))
    End Sub
End Class

The results of the execution are as follows:

augmenter(4,7)
n=18
ajouter(2,3)
n=5
soustraire(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(liste d'arguments)
Public Event événement as proc

or (2)

Public Event événement(liste d'arguments)

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 management
Imports System
 
Public Class myEventArgs
    Inherits EventArgs
    ' the class of an evt
    ' attribute
    Private _saisie As String
 
    ' manufacturer
    Public Sub New(ByVal saisie As String)
        _saisie = saisie
    End Sub
 
    ' read-only property
    Public Overrides Function ToString() As String
        Return _saisie
    End Function
End Class
 
Public Class émetteur
    ' the class issuing an event
    ' attribute
    Private _nom As String    ' name of issuer
 
    ' manufacturer
    Public Sub New(ByVal nom As String)
        _nom = nom
    End Sub
 
    ' ToString
    Public Overrides Function ToString() As String
        Return _nom
    End Function
 
    ' the prototype of the functions responsible for processing the event
    Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
 
    ' pool of event managers
    Public Event evtHandler As _evtHandler
 
    ' method of requesting the issue of an evt
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' all subscribers are notified
        ' we act as if the event originated here
        RaiseEvent evtHandler(Me, evt)
    End Sub
End Class
 
' an evt treatment class
Public Class souscripteur
    ' attribute
    Private nom As String    ' name of subscriber
    Private sender As émetteur    ' evts sender
 
    ' manufacturer
    Public Sub New(ByVal nom As String, ByVal e As émetteur)
        ' we note the name of the subscriber
        Me.nom = nom
        ' and the sender of the evts
        Me.sender = e
        ' register to receive news from the transmitter e
        AddHandler e.evtHandler, AddressOf traitementEvt
    End Sub
 
    ' event manager
    Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
        ' evt display
        Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
    End Sub
End Class
 
' a test program
Public Class test
    Public Shared Sub Main()
        ' creation of an evts transmitter
        Dim émetteur1 As New émetteur("émetteur1")
        ' create a table of subscribers
        ' for events issued by issuer1
        Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
        ' read a sequence of integers from the keyboard
        ' as soon as one is wrong, we issue an evt
        Console.Out.Write("Nombre entier (rien pour arrêter) : ")
        Dim saisie As String = Console.In.ReadLine().Trim()
        ' as long as the line entered is not empty
        While saisie <> ""
            ' is the input a whole number?
            Try
                Dim n As Integer = Integer.Parse(saisie)
            Catch
                ' not an integer
                ' we warn everyone
                émetteur1.envoyerEvt(New myEventArgs(saisie))
            End Try
            ' we warn everyone
            ' new entry
            Console.Out.Write("Nombre entier (rien pour arrêter) : ")
            saisie = 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 émetteur
    ' the class issuing an event
    ' attribute
    Private _nom As String    ' name of issuer
 
    ' manufacturer
    Public Sub New(ByVal nom As String)
        _nom = nom
    End Sub
 
    ' ToString
    Public Overrides Function ToString() As String
        Return _nom
    End Function
 
    ' the prototype of the functions responsible for processing the event
    Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
 
    ' pool of event managers
    Public Event evtHandler As _evtHandler
 
    ' method of requesting the issue of an evt
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' all subscribers are notified
        ' we 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 processing 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 of requesting the issue of an evt
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' all subscribers are notified
        ' 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 evt
    ' attribute
    Private _saisie As String
 
    ' manufacturer
    Public Sub New(ByVal saisie As String)
        _saisie = saisie
    End Sub
 
    ' read-only property
    Public Overrides Function ToString() As String
        Return _saisie
    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 evt treatment class
    Public Class souscripteur
        ' attribute
        Private nom As String     ' name of subscriber
        Private sender As émetteur     ' evts sender
 
        ' manufacturer
        Public Sub New(ByVal nom As String, ByVal e As émetteur)
            ' we note the name of the subscriber
            Me.nom = nom
            ' and the sender of the evts
            Me.sender = e
            ' register to receive news from the transmitter e
            AddHandler e.evtHandler, AddressOf traitementEvt
        End Sub
 
        ' event manager
        Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
            ' evt display
            Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
        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:


            ' on s'inscrit pour recevoir les evts de l'émetteur e
            AddHandler e.evtHandler, AddressOf traitementEvt

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()
            ' creation of an evts transmitter
            Dim émetteur1 As New émetteur("émetteur1")
            ' create a table of subscribers
            ' for events issued by issuer1
            Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
            ' read a sequence of integers from the keyboard
            ' as soon as one is wrong, we issue an evt
            Console.Out.Write("Nombre entier (rien pour arrêter) : ")
            Dim saisie As String = Console.In.ReadLine().Trim()
            ' as long as the line entered is not empty
            While saisie <> ""
                ' is the input a whole number?
                Try
                    Dim n As Integer = Integer.Parse(saisie)
                Catch
                    ' not an integer
                    ' we warn everyone
                    émetteur1.envoyerEvt(New myEventArgs(saisie))
                End Try
                ' we warn everyone
                ' new entry
                Console.Out.Write("Nombre entier (rien pour arrêter) : ")
                saisie = Console.In.ReadLine().Trim()
            End While
            ' end
            Environment.Exit(0)
        End Sub

We create an event emitter object:


        ' creation of an evts transmitter
        Dim émetteur1 As New émetteur("émetteur1")

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


        ' create a table of subscribers
        ' for events issued by issuer1
        Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}

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:


                ' on prévient tout le monde
                émetteur1.envoyerEvt(New myEventArgs(saisie))

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
Nombre entier (rien pour arrêter) : 4
Nombre entier (rien pour arrêter) : a
L'objet [émetteur1] a signalé la saisie erronée [a] au souscripteur [s1]
L'objet [émetteur1] a signalé la saisie erronée [a] au souscripteur [s2]
Nombre entier (rien pour arrêter) : 1.6
L'objet [émetteur1] a signalé la saisie erronée [1.6] au souscripteur [s1]
L'objet [émetteur1] a signalé la saisie erronée [1.6] au souscripteur [s2]
Nombre entier (rien pour arrêter) :