Skip to content

6. Gebeurtenissen beheren

In het vorige hoofdstuk hebben we het begrip ‘gebeurtenissen gekoppeld aan componenten’ besproken. We gaan nu bekijken hoe we gebeurtenissen in onze eigen klassen kunnen aanmaken. Dit is een moeilijk begrip dat niet interessant is voor beginnende ontwikkelaars; zij kunnen beter direct doorgaan naar het volgende hoofdstuk.

6.1. Delegate-objecten

De instructie

    Delegate Function opération(ByVal n1 As Integer, ByVal n2 As Integer) As Integer

definieert een type met de naam opération, dat in feite een functieprototype is dat twee gehele getallen accepteert en een geheel getal retourneert. Het is het sleutelwoord delegate dat van opération een functieprototypedefinitie maakt. Het type [opération] kan als volgt aan een variabele worden toegewezen:


        Dim op As New opération(AddressOf [fonction])

Een variabele van het type [délégué] wordt als een object aangemaakt. De parameter van de constructor is de functie die aan de instantie van de delegate is gekoppeld. In VB wordt deze functie opgegeven in de vorm AddressOf functie, waarbij [fonction] de naam van de functie is. Zodra de instantie van een delegate is aangemaakt, kan deze als een functie worden gebruikt:


        ' de gedelegeerde functie wordt uitgevoerd
        Dim n As Integer = op(4, 7)

De delegate [opération] is gedefinieerd als een functie die twee parameters van het type [integer] ontvangt en een resultaat van hetzelfde type retourneert. De hierboven genoemde instantie [op] wordt dus als een dergelijke functie gebruikt. Laten we het volgende voorbeeld bekijken:


' gedelegeerde functies
Imports System

Public Class delegate1
    ' definitie van een functieprototype
    ' neemt 2 gehele getallen als parameter en retourneert een geheel getal
    Delegate Function opération(ByVal n1 As Integer, ByVal n2 As Integer) As Integer

    ' twee instantiemethoden die overeenkomen met het prototype
    ' toevoegen
    Public Function ajouter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
        Console.Out.WriteLine(("ajouter(" & n1 & "," & n2 & ")"))
        Return n1 + n2
    End Function

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

    ' een statische methode die overeenkomt met het 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

    ' testprogramma
    Public Shared Sub Main()
        ' we definiëren een object van het type ‘operatie’ om daar functies in op te slaan
        ' de statische functie 'verhogen' wordt opgeslagen
        Dim op As New opération(AddressOf delegate1.augmenter)
        ' de delegate wordt uitgevoerd
        Dim n As Integer = op(4, 7)
        Console.Out.WriteLine(("n=" & n))
        ' aanmaken van een object c1 van het type class1
        Dim c1 As New delegate1
        ' de methode ‘toevoegen’ van c1 wordt in de delegate geregistreerd
        op = New delegate1.opération(AddressOf c1.ajouter)
        ' het delegatieobject wordt uitgevoerd
        n = op(2, 3)
        Console.Out.WriteLine(("n=" & n))
        ' de methode 'aftrekken' van c1 wordt in de delegate opgeslagen
        op = New delegate1.opération(AddressOf c1.soustraire)
        n = op(2, 3)
        Console.Out.WriteLine(("n=" & n))
    End Sub
End Class

De resultaten van de uitvoering zijn als volgt:

augmenter(4,7)
n=18
ajouter(2,3)
n=5
soustraire(2,3)
n=-1

6.2. Gebeurtenisbeheer

Waarvoor kan een object van het type delegate worden gebruikt?

Zoals we in het volgende voorbeeld zullen zien, wordt het vooral gebruikt voor het beheer van gebeurtenissen. Een klasse van het type C1 kan gebeurtenissen van het type evti genereren. Bij een gebeurtenis van het type evti zal een object van het type C1 de uitvoering van een object van het type evtiDéclenché (dat zelf van het type delegate is) starten. Alle functies die zijn geregistreerd in het delegate-object evtiDéclenché worden dan uitgevoerd. Als een object van het type C2, dat gebruikmaakt van een object van het type C1, op de hoogte wil worden gesteld van het optreden van de gebeurtenis evti op het object van het type C1, zal het een van zijn methoden C2.f registreren in het gedelegeerde object C1.evtiDéclenché van het object C1, zodat de methode C2.f wordt uitgevoerd telkens wanneer degebeurtenis evti zich voordoet op het object C1. Aangezien het gedelegeerde object C1.evtiDéclenché meerdere functies kan registreren, kunnen verschillende objecten Ci zich registreren bij de delegatie C1.evtiDéclenché om op de hoogte te worden gesteld van de gebeurtenis evti op C1.

6.2.1. Aangifte van een gebeurtenis

Een gebeurtenis wordt als volgt gemeld (1):


Delegate Sub proc(liste d'arguments)
Public Event événement as proc

of (2)


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

Het trefwoord [Event] definieert een gegeven als een gebeurtenis. Aan de gebeurtenis wordt de handtekening gekoppeld die de handlers ervan moeten hebben. Deze handtekening kan worden gedefinieerd door middel van een delegate (methode 1) of rechtstreeks (methode 2).

6.2.2. De handlers van een gebeurtenis definiëren

Wanneer een gebeurtenis wordt geactiveerd door de instructie [RaiseEvent], moet deze worden verwerkt. De procedures die verantwoordelijk zijn voor het verwerken van gebeurtenissen worden gebeurtenishandlers genoemd. Bij het aanmaken van een object [Event] wordt alleen de signatuur van de gebeurtenishandlers gedeclareerd. Deze signatuur is die van het object [delegate] dat aan de gebeurtenis is gekoppeld. Zodra dit is gebeurd, kunnen er handlers aan de gebeurtenis worden gekoppeld. Dit zijn procedures die dezelfde handtekening moeten hebben als het aan de gebeurtenis gekoppelde [delegate]-object. Om een gebeurtenishandler aan een gebeurtenis te koppelen, gebruikt men de instructie [AddHandler] met de volgende syntaxis:

AddHandler event, AddressOf eventhandler
  • event: gebeurtenisvariabele waaraan een nieuwe gebeurtenishandler wordt gekoppeld
  • eventhandler: naam van de gebeurtenishandler – moet dezelfde handtekening hebben als de aan de gebeurtenis gekoppelde [délégué]

Er kunnen zoveel handlers aan een gebeurtenis worden gekoppeld als men wil. Ze worden allemaal uitgevoerd wanneer de gebeurtenis waaraan ze zijn gekoppeld, wordt geactiveerd.

6.2.3. Een gebeurtenis activeren

Om een gebeurtenis programmatisch te activeren, gebruikt men de instructie [RaiseEvent]:

RaiseEvent eventname[( argumentlist )]
  • eventname: gebeurtenisvariabele (gedeclareerd met het sleutelwoord Event)
  • argumentlist: argumenten van de aan de gebeurtenis gekoppelde delegatie.

Alle handlers die via de instructie AddHandler aan de gebeurtenis zijn gekoppeld, worden aangeroepen. Zij ontvangen als parameters de parameters die in de handtekening van de gebeurtenis zijn gedefinieerd.

6.2.4. Een voorbeeld

Laten we het volgende voorbeeld eens bekijken:


'gebeurtenisafhandeling
Imports System

Public Class myEventArgs
    Inherits EventArgs
    ' de klasse van een gebeurtenis
    ' attribuut
    Private _saisie As String

    ' constructor
    Public Sub New(ByVal saisie As String)
        _saisie = saisie
    End Sub

    ' alleen-lezen eigenschap
    Public Overrides Function ToString() As String
        Return _saisie
    End Function
End Class

Public Class émetteur
    ' de klasse die een gebeurtenis verzendt
    ' attribuut
    Private _nom As String    ' nom de l'émetteur

    ' constructor
    Public Sub New(ByVal nom As String)
        _nom = nom
    End Sub

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

    ' het prototype van de functies die de gebeurtenis verwerken
    Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)

    ' de pool van gebeurtenisverwerkers
    Public Event evtHandler As _evtHandler

    ' methode voor het aanvragen van het verzenden van een gebeurtenis
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' alle abonnees worden op de hoogte gebracht
        ' er wordt gedaan alsof de gebeurtenis hier vandaan komt
        RaiseEvent evtHandler(Me, evt)
    End Sub
End Class

' een klasse voor de verwerking van de gebeurtenis
Public Class souscripteur
    ' attribuut
    Private nom As String    ' nom du souscripteur
    Private sender As émetteur    ' l'émetteur des évts

    ' constructor
    Public Sub New(ByVal nom As String, ByVal e As émetteur)
        ' we noteren de naam van de abonnee
        Me.nom = nom
        ' en de verzender van de gebeurtenissen
        Me.sender = e
        ' men schrijft zich in om de gebeurtenissen van de verzender te ontvangen
        AddHandler e.evtHandler, AddressOf traitementEvt
    End Sub

    ' gebeurtenisbeheerder
    Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
        ' gebeurtenissen weergeven
        Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
    End Sub
End Class

' een testprogramma
Public Class test
    Public Shared Sub Main()
        ' aanmaken van een event-zender
        Dim émetteur1 As New émetteur("émetteur1")
        ' aanmaken van een lijst met abonnees
        ' voor de gebeurtenissen die door zender1 worden verzonden
        Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
        ' een reeks gehele getallen wordt ingevoerd via het toetsenbord
        ' zodra er een foutief getal wordt ingevoerd, wordt er een gebeurtenis verzonden
        Console.Out.Write("Nombre entier (rien pour arrêter) : ")
        Dim saisie As String = Console.In.ReadLine().Trim()
        ' zolang de ingevoerde regel niet leeg is
        While saisie <> ""
            ' is de invoer een geheel getal?
            Try
                Dim n As Integer = Integer.Parse(saisie)
            Catch
                ' het is geen geheel getal
                ' iedereen wordt gewaarschuwd
                émetteur1.envoyerEvt(New myEventArgs(saisie))
            End Try
            ' iedereen wordt gewaarschuwd
            ' nieuwe invoer
            Console.Out.Write("Nombre entier (rien pour arrêter) : ")
            saisie = Console.In.ReadLine().Trim()
        End While
        ' einde
        Environment.Exit(0)
    End Sub
End Class

De bovenstaande code is vrij complex. Laten we deze eens nader bekijken. In een gebeurtenisbeheer is er een gebeurtenisverzender (sender) die de details van de gebeurtenissen (EventArgs) verstuurt naar abonnees die hebben aangegeven geïnteresseerd te zijn in de betreffende gebeurtenissen. De klasse die de gebeurtenissen verzendt, is hier als volgt:


Public Class émetteur
    ' de klasse die een gebeurtenis genereert
    ' attribuut
    Private _nom As String    ' nom de l'émetteur

    ' constructor
    Public Sub New(ByVal nom As String)
        _nom = nom
    End Sub

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

    ' het prototype van de functies die de gebeurtenis verwerken
    Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)

    ' de pool van gebeurtenisverwerkers
    Public Event evtHandler As _evtHandler

    ' methode voor het aanvragen van het verzenden van een gebeurtenis
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' alle abonnees worden op de hoogte gebracht
        ' er wordt gedaan alsof de gebeurtenis hier vandaan komt
        RaiseEvent evtHandler(Me, evt)
    End Sub
End Class

Elk object émetteur heeft een nom dat bij aanmaak wordt vastgelegd. De methode ToString is zodanig geherdefinieerd dat bij het omzetten van een object émetteur naar string, de naam ervan wordt overgenomen. De klasse definieert een gebeurtenis:


        ' het prototype van de functies die de gebeurtenis verwerken
        Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)

        ' de gebeurtenis
        Public Event evtHandler As _evtHandler

Het eerste argument van een functie die een gebeurtenis verwerkt, is het object dat de gebeurtenis genereert. Het tweede argument is van het type myEventArgs, een object dat details over de gebeurtenis geeft en waarop we later terugkomen. Om een gebeurtenis van buitenaf te kunnen activeren vanuit een object émetteur, voegen we de methode envoyerEvt toe aan de klasse:


    ' methode om het genereren van een gebeurtenis aan te vragen
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' alle abonnees worden op de hoogte gebracht
        ' er wordt gedaan alsof de gebeurtenis hier vandaan komt
        RaiseEvent evtHandler(Me, evt)
    End Sub

We moeten definiëren welk type gebeurtenissen door de klasse émetteur worden geactiveerd. Hiervoor hebben we de klasse myEventArgs gedefinieerd:


Public Class myEventArgs
    ' de klasse van een gebeurtenis
    ' attribuut
    Private _saisie As String

    ' constructor
    Public Sub New(ByVal saisie As String)
        _saisie = saisie
    End Sub

    ' alleen-lezen eigenschap
    Public Overrides Function ToString() As String
        Return _saisie
    End Function
End Class

De gebeurtenissen die voor ons van belang zijn, zijn foutieve toetsaanslagen. We vragen een gebruiker om gehele getallen in te voeren via het toetsenbord en zodra hij een tekenreeks invoert die geen geheel getal vertegenwoordigt, wordt er een gebeurtenis geactiveerd. Als detail van de gebeurtenis volstaat het om de foutieve invoer te vermelden. Dit is de betekenis van het attribuut _saisie van de klasse. Er wordt dus een object myEventArgs aangemaakt met de foutieve invoer als parameter. Daarnaast herdefiniëren we de methode ToString, zodat wanneer een object myEventArgs in een tekenreeks wordt omgezet, het attribuut _saisie wordt verkregen.

We hebben de verzender van de gebeurtenissen en het type gebeurtenissen dat deze verzendt gedefinieerd. Vervolgens definiëren we het type abonnees dat geïnteresseerd is in deze gebeurtenissen.


    ' een klasse voor de afhandeling van de gebeurtenis
    Public Class souscripteur
        ' attribuut
        Private nom As String     ' nom du souscripteur
        Private sender As émetteur     ' l'émetteur des évts

        ' constructor
        Public Sub New(ByVal nom As String, ByVal e As émetteur)
            ' de naam van de abonnee wordt genoteerd
            Me.nom = nom
            ' en de verzender van de gebeurtenissen
            Me.sender = e
            ' men schrijft zich in om de gebeurtenissen van de verzender te ontvangen
            AddHandler e.evtHandler, AddressOf traitementEvt
        End Sub

        ' gebeurtenisbeheerder
        Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
            ' gebeurtenisweergave
            Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
        End Sub
    End Class

Een abonnee wordt gedefinieerd door twee parameters: zijn naam (attribuut nom) en het object émetteur waarvan hij de gebeurtenissen wil verwerken (attribuut sender). Deze twee parameters worden doorgegeven aan de constructor van het object. Tijdens dezezelfde constructie abonneert de abonnee zich op de gebeurtenissen van de zender:


            ' je schrijft je in om de gebeurtenissen van de zender e te ontvangen
            AddHandler e.evtHandler, AddressOf traitementEvt

De bij de zender geregistreerde functie is traitementEvt. Deze methode van de klasse souscripteur geeft de twee ontvangen argumenten weer (sender, evt) evenals de naam van de ontvanger (nom). Het type van de gegenereerde gebeurtenissen, het type van de verzender van deze gebeurtenissen en het type van de abonnees zijn gedefinieerd. We hoeven ze nu alleen nog maar te implementeren:


    ' een testprogramma
    Public Class test
        Public Shared Sub Main()
            ' een gebeurtenisverzender aanmaken
            Dim émetteur1 As New émetteur("émetteur1")
            ' aanmaken van een lijst met abonnees
            ' voor de gebeurtenissen die door zender1 worden verzonden
            Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
            ' een reeks gehele getallen wordt ingevoerd via het toetsenbord
            ' zodra er een foutief getal wordt ingevoerd, wordt er een gebeurtenis verzonden
            Console.Out.Write("Nombre entier (rien pour arrêter) : ")
            Dim saisie As String = Console.In.ReadLine().Trim()
            ' zolang de ingevoerde regel niet leeg is
            While saisie <> ""
                ' is de invoer een geheel getal?
                Try
                    Dim n As Integer = Integer.Parse(saisie)
                Catch
                    ' het is geen geheel getal
                    ' iedereen wordt gewaarschuwd
                    émetteur1.envoyerEvt(New myEventArgs(saisie))
                End Try
                ' iedereen wordt gewaarschuwd
                ' nieuwe invoer
                Console.Out.Write("Nombre entier (rien pour arrêter) : ")
                saisie = Console.In.ReadLine().Trim()
            End While
            ' einde
            Environment.Exit(0)
        End Sub

We maken een object émetteur aan:


         ' een gebeurtenisverzender aanmaken
        Dim émetteur1 As New émetteur("émetteur1")

We maken een tabel met twee abonnees voor de gebeurtenissen die worden verzonden door het object émetteur1:


         ' aanmaken van een lijst met abonnees
         ' voor gebeurtenissen verzonden door zender1
        Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}

We vragen de gebruiker om gehele getallen via het toetsenbord in te voeren. Zodra er een foutieve invoer plaatsvindt, vragen we émetteur1 om een gebeurtenis naar zijn abonnees te verzenden:


                 ' iedereen wordt op de hoogte gebracht
                émetteur1.envoyerEvt(New myEventArgs(saisie))

De verzonden gebeurtenis is van het type myEventArgs en bevat de foutieve invoer. Beide abonnees zouden deze gebeurtenis moeten ontvangen en melden. Dit wordt geïllustreerd in de volgende uitvoering.

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