Skip to content

6. Ereignisbehandlung

Im vorigen Kapitel haben wir das Konzept von Ereignissen im Zusammenhang mit Komponenten behandelt. Wir werden nun sehen, wie man Ereignisse in eigenen Klassen erstellt. Dies ist ein schwieriges Konzept, das für Anfänger nicht von Interesse ist; ihnen wird empfohlen, direkt zum nächsten Kapitel überzugehen.

6.1. Delegatobjekte

Die Anweisung

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

definiert einen Typ namens operation, bei dem es sich eigentlich um einen Funktionsprototyp handelt, der zwei Ganzzahlen akzeptiert und eine Ganzzahl zurückgibt. Es ist das Schlüsselwort delegate, das operation zu einer Funktionsprototypdefinition macht. Der Typ [operation] kann einer Variablen wie folgt zugewiesen werden:


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

Eine Variable vom Typ [delegate] wird als Objekt instanziiert. Der Konstruktorparameter ist die Funktion, die der Delegate-Instanz zugeordnet ist. In VB wird diese Funktion als AddressOf function angegeben, wobei [function] der Name der Funktion ist. Sobald eine Delegate-Instanz erstellt wurde, kann sie als Funktion verwendet werden:


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

Der Delegat [operation] wurde so definiert, dass er zwei Parameter vom Typ [integer] entgegennimmt und ein Ergebnis desselben Typs zurückgibt. Daher wird seine Instanz [op] oben als eine solche Funktion verwendet. Betrachten Sie das folgende Beispiel:


' 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

Die Ergebnisse der Ausführung lauten wie folgt:

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

6.2. Ereignisbehandlung

Wofür kann ein Delegatobjekt verwendet werden?

Wie wir im folgenden Beispiel sehen werden, wird es in erster Linie zur Ereignisbehandlung verwendet. Eine Klasse C1 kann evti-Ereignisse generieren. Wenn ein evti-Ereignis eintritt, löst ein Objekt vom Typ C1 die Ausführung eines evtiDéclenché-Objekts vom Typ Delegate aus. Alle im Delegate-Objekt evtiDéclenché registrierten Funktionen werden dann ausgeführt. Wenn ein Objekt C2, das ein Objekt C1 verwendet, über das Auftreten des evti-Ereignisses auf Objekt C1 benachrichtigt werden möchte, registriert es eine seiner Methoden, C2.f, beim Delegate-Objekt C1.evtiDéclenché von Objekt C1, sodass die Methode C2.f jedes Mal ausgeführt wird, wenn das evti-Ereignis auf Objekt C1 auftritt. Da das Delegatobjekt C1.evtiDéclenché mehrere Funktionen registrieren kann, können sich verschiedene Objekte Ci beim Delegaten C1.evtiDéclenché registrieren, um über das Ereignis evti auf C1 benachrichtigt zu werden.

6.2.1. Deklarieren eines Ereignisses

Ein Ereignis wird wie folgt deklariert (1):

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

oder (2)

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

Das Schlüsselwort [Event] definiert ein Datenelement als Ereignis. Das Ereignis ist mit der Signatur verknüpft, die seine Handler aufweisen müssen. Diese Signatur kann mithilfe eines Delegaten (Methode 1) oder direkt (Methode 2) definiert werden.

6.2.2. Definition von Ereignisbehandlungsroutinen

Wenn ein Ereignis durch die Anweisung [RaiseEvent] ausgelöst wird, muss es behandelt werden. Die für die Behandlung von Ereignissen zuständigen Prozeduren werden als Ereignis-Handler bezeichnet. Beim Erstellen eines [Event]-Objekts wird nur die Signatur der Ereignis-Handler deklariert. Diese Signatur ist die des mit dem Ereignis verknüpften [delegate]-Objekts. Sobald dies geschehen ist, können dem Ereignis Handler zugeordnet werden. Dabei handelt es sich um Prozeduren, die dieselbe Signatur wie der mit dem Ereignis verknüpfte [delegate] haben müssen. Um einen Ereignis-Handler einem Ereignis zuzuordnen, verwenden Sie die Anweisung [AddHandler] mit der folgenden Syntax:

AddHandler event, AddressOf eventhandler
  • event: Ereignisvariable, der ein neuer Ereignis-Handler zugeordnet wird
  • Ereignisbehandler: Name des Ereignisbehandlers – muss dieselbe Signatur haben wie der mit dem Ereignis verknüpfte [Delegat]

Sie können einem Ereignis beliebig viele Handler zuordnen. Diese werden alle ausgeführt, wenn das Ereignis, dem sie zugeordnet sind, ausgelöst wird.

6.2.3. Auslösen eines Ereignisses

Um ein Ereignis programmgesteuert auszulösen, verwenden Sie die Anweisung [RaiseEvent]:

RaiseEvent eventname[( argumentlist )]
  • eventname: Ereignisvariable (deklariert mit dem Schlüsselwort Event)
  • Argumentliste: Argumente des mit dem Ereignis verknüpften Delegaten.

Alle Handler, die dem Ereignis mithilfe der Anweisung AddHandler zugeordnet wurden, werden aufgerufen. Sie erhalten als Parameter die in der Ereignissignatur definierten Parameter.

6.2.4. Ein Beispiel

Betrachten Sie das folgende Beispiel:


'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

Der obige Code ist recht komplex. Schauen wir ihn uns genauer an. Bei der Ereignisbehandlung gibt es einen Ereignissender, der Ereignisdetails (EventArgs) an Abonnenten sendet, die Interesse an den betreffenden Ereignissen bekundet haben. Die Klasse, die hier das Ereignis sendet, sieht wie folgt aus:


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

Jedes Emitter-Objekt hat standardmäßig einen Namen. Die ToString-Methode wurde so umdefiniert, dass beim Konvertieren eines Emitter-Objekts in eine Zeichenfolge dessen Name zurückgegeben wird. Die Klasse definiert ein Ereignis:


        ' 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

Das erste Argument einer Ereignisbehandlungsfunktion ist das Objekt, das das Ereignis auslöst. Das zweite Argument ist vom Typ myEventArgs, ein Objekt, das Details zum Ereignis enthält, auf die wir später noch eingehen werden. Um ein Ereignis von außerhalb eines Emitter-Objekts auszulösen, fügen wir der Klasse die Methode envoyerEvt hinzu:


    ' 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

Wir müssen den Typ der Ereignisse definieren, die von der Emitter-Klasse ausgelöst werden. Dazu haben wir die Klasse myEventArgs definiert:


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

Die Ereignisse, die uns interessieren, sind fehlerhafte Tastatureingaben. Wir werden einen Benutzer auffordern, ganze Zahlen über die Tastatur einzugeben, und sobald er eine Zeichenfolge eingibt, die keine ganze Zahl darstellt, lösen wir ein Ereignis aus. Als Ereignisdetail geben wir einfach die fehlerhafte Eingabe an. Dies ist der Zweck des Attributs _input der Klasse. Daher wird ein myEventArgs-Objekt mit der fehlerhaften Eingabe als Parameter erstellt. Außerdem überschreiben wir die ToString-Methode, sodass wir bei der Konvertierung eines myEventArgs-Objekts in eine Zeichenfolge die _input-Eigenschaft erhalten.

Wir haben den Ereignis-Emitter und die Art der von ihm ausgelösten Ereignisse definiert. Als Nächstes definieren wir die Art der Abonnenten, die an diesen Ereignissen interessiert sind.


    ' 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

Ein Abonnent wird durch zwei Parameter definiert: seinen Namen (Attribut „name“) und das Emitter-Objekt, dessen Ereignisse er verarbeiten möchte (Attribut „sender“). Diese beiden Parameter werden an den Konstruktor des Objekts übergeben. Während dieser Konstruktion abonniert der Abonnent die Ereignisse des Emitters:


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

Die beim Publisher registrierte Funktion ist traitementEvt. Diese Methode der Subscriber-Klasse zeigt die beiden empfangenen Argumente (sender, evt) sowie den Namen des Empfängers (nom) an. Die Typen der erzeugten Ereignisse, der Typ des Publishers dieser Ereignisse und die Typen der Subscriber wurden definiert. Nun müssen sie nur noch implementiert werden:


    ' 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

Wir erstellen ein Ereignis-Emitter-Objekt:


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

Wir erstellen ein Array mit zwei Abonnenten für die vom Objekt emetteur1 ausgesendeten Ereignisse:


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

Wir bitten den Benutzer, über die Tastatur Ganzzahlen einzugeben. Sobald eine falsche Eingabe erfolgt, bitten wir issuer1, ein Ereignis an seine Abonnenten zu senden:


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

Das gesendete Ereignis ist vom Typ myEventArgs und enthält die fehlerhafte Eingabe. Beide Abonnenten sollten dieses Ereignis erhalten und melden. Dies wird in der folgenden Ausführung gezeigt.

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