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
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:
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):
or (2)
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:
- 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:
- 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) :