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 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:
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:
- 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 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):