Skip to content

6. Ereignisverwaltung

Im vorigen Kapitel haben wir das Konzept der komponentenbezogenen Ereignisse behandelt. Nun sehen wir uns an, wie man Ereignisse in eigenen Klassen erstellt. Dies ist ein schwieriges Thema, das für Anfänger nicht von Interesse ist; diesen wird empfohlen, direkt zum nächsten Kapitel überzugehen.

6.1. Delegate-Objekte

Die Anweisung

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

definiert einen Typ namens opération, bei dem es sich tatsächlich um einen Funktionsprototyp handelt, der zwei Ganzzahlen entgegennimmt und eine Ganzzahl zurückgibt. Es ist das Schlüsselwort „delegate“, das opération zu einer Funktionsprototypdefinition macht. Der Typ [opération] kann einer Variablen wie folgt zugewiesen werden:


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

Eine Variable vom Typ [délégué] wird als Objekt instanziiert. Der Konstruktorparameter ist die Funktion, die der Delegat-Instanz zugeordnet ist. In VB wird diese Funktion in der Form AddressOf-Funktion angegeben, wobei [fonction] der Name der Funktion ist. Sobald die Instanz eines Delegaten erstellt wurde, kann sie wie eine Funktion verwendet werden:


        ' Delegierte Funktion wird ausgeführt
        Dim n As Integer = op(4, 7)

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


' Delegierte Funktionen
Imports System

Public Class delegate1
    ' Definition eines Funktionsprototyps
    ' nimmt 2 Ganzzahlen als Parameter entgegen und gibt eine Ganzzahl zurück
    Delegate Function opération(ByVal n1 As Integer, ByVal n2 As Integer) As Integer

    ' zwei Instanzmethoden, die dem Prototyp entsprechen
    ' addieren
    Public Function ajouter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
        Console.Out.WriteLine(("ajouter(" & n1 & "," & n2 & ")"))
        Return n1 + n2
    End Function

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

    ' eine statische Methode, die dem Prototyp entspricht
    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

    ' Testprogramm
    Public Shared Sub Main()
        ' Es wird ein Objekt vom Typ „Operation“ definiert, um darin Funktionen zu speichern
        ' Die statische Funktion „erhöhen“ wird registriert
        Dim op As New opération(AddressOf delegate1.augmenter)
        ' Der Delegat wird ausgeführt
        Dim n As Integer = op(4, 7)
        Console.Out.WriteLine(("n=" & n))
        ' Erstellung eines Objekts c1 vom Typ class1
        Dim c1 As New delegate1
        ' Die Methode „addieren“ von c1 wird im Delegaten registriert
        op = New delegate1.opération(AddressOf c1.ajouter)
        ' Ausführung des Delegaten-Objekts
        n = op(2, 3)
        Console.Out.WriteLine(("n=" & n))
        ' Die Methode „subtrahieren“ von c1 wird im Delegaten gespeichert
        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. Ereignisverwaltung

Wozu kann ein Objekt vom Typ delegate dienen?

Wie wir im folgenden Beispiel sehen werden, dient dies vor allem der Ereignisverwaltung. Eine Klasse vom Typ C1 kann Ereignisse vom Typ evti generieren. Bei einem Ereignis vom Typ evti löst ein Objekt vom Typ C1 die Ausführung eines Objekts vom Typ evtiDéclenché aus, das wiederum vom Typ delegate ist. Alle im Delegatobjekt evtiDéclenché registrierten Funktionen werden dann ausgeführt. Wenn ein Objekt vom Typ C2, das ein Objekt vom Typ C1 verwendet, über das Auftreten des Ereignisses evti am Objekt C1 benachrichtigt werden soll, registriert es eine seiner Methoden C2.f im Delegatenobjekt C1.evtiDéclenché des Objekts C1, damit die Methode C2.f jedes Mal ausgeführt wird, wenn dasEreignis evti am Objekt C1 auftritt. Da das Delegatobjekt C1.evtiDéclenché mehrere Funktionen registrieren kann, können sich verschiedene Objekte vom Typ Ci beim Delegaten C1.evtiDéclenché registrieren, um über das Ereignis evti auf C1 benachrichtigt zu werden.

6.2.1. Meldung eines Ereignisses

Ein Ereignis wird wie folgt gemeldet (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 einen Datensatz als Ereignis. Dem Ereignis wird die Signatur zugeordnet, die die Handler dieses Ereignisses aufweisen müssen. Diese Signatur kann entweder über einen Delegaten (Methode 1) oder direkt (Methode 2) definiert werden.

6.2.2. Handler für ein Ereignis definieren

Wenn ein Ereignis durch die Anweisung [RaiseEvent] ausgelöst wird, muss es verarbeitet werden. Die Prozeduren, die für die Verarbeitung der Ereignisse zuständig sind, werden als Ereignisbehandler bezeichnet. Bei der Erstellung eines Objekts [Event] wird lediglich die Signatur der Ereignisbehandler deklariert. Diese Signatur entspricht der des Objekts [delegate], das dem Ereignis zugeordnet ist. Anschließend können dem Ereignis Handler zugeordnet werden. Dabei handelt es sich um Prozeduren, die dieselbe Signatur wie das dem Ereignis zugeordnete Objekt [delegate] aufweisen müssen. Um einem Ereignis einen Ereignis-Handler zuzuordnen, verwendet man die Anweisung [AddHandler] mit folgender Syntax:

AddHandler event, AddressOf eventhandler
  • event: Ereignisvariable, der ein neuer Ereignisbehandler zugeordnet wird
  • eventhandler: Name des Ereignisbehandlers – muss die Signatur des mit dem Ereignis verknüpften [délégué] aufweisen

Einem Ereignis können beliebig viele Handler zugeordnet werden. Sie werden alle ausgeführt, sobald das Ereignis, dem sie zugeordnet sind, ausgelöst wird.

6.2.3. Ein Ereignis auslösen

Um ein Ereignis programmgesteuert auszulösen, verwendet man die Anweisung [RaiseEvent]:

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

Alle Handler, die dem Ereignis über die Anweisung AddHandler zugeordnet wurden, werden aufgerufen. Sie erhalten als Parameter die in der Signatur des Ereignisses definierten Parameter.

6.2.4. Ein Beispiel

Betrachten wir das folgende Beispiel:


'Ereignisbehandlung
Imports System

Public Class myEventArgs
    Inherits EventArgs
    ' Die Klasse eines Ereignisses
    ' Attribut
    Private _saisie As String

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

    ' schreibgeschützte Eigenschaft
    Public Overrides Function ToString() As String
        Return _saisie
    End Function
End Class

Public Class émetteur
    ' die Klasse, die ein Ereignis auslöst
    ' Attribut
    Private _nom As String    ' nom de l'émetteur

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

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

    ' der Prototyp der Funktionen zur Verarbeitung des Ereignisses
    Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)

    ' der Pool der Ereignisbehandler
    Public Event evtHandler As _evtHandler

    ' Methode zur Anforderung der Auslösung eines Ereignisses
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' Alle Abonnenten werden benachrichtigt
        ' Es wird so getan, als stamme das Ereignis von hier
        RaiseEvent evtHandler(Me, evt)
    End Sub
End Class

' Eine Klasse zur Verarbeitung des Ereignisses
Public Class souscripteur
    ' Attribut
    Private nom As String    ' nom du souscripteur
    Private sender As émetteur    ' l'émetteur des évts

    ' Konstruktor
    Public Sub New(ByVal nom As String, ByVal e As émetteur)
        ' Wir notieren den Namen des Abonnenten
        Me.nom = nom
        ' sowie den Absender der Ereignisse
        Me.sender = e
        ' man registriert sich, um die Ereignisse des Emittenten e zu empfangen
        AddHandler e.evtHandler, AddressOf traitementEvt
    End Sub

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

' ein Testprogramm
Public Class test
    Public Shared Sub Main()
        ' Erstellung eines Ereignis-Senders
        Dim émetteur1 As New émetteur("émetteur1")
        ' Erstellung einer Abonnentenliste
        ' für die von Sender1 gesendeten Ereignisse
        Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
        ' Eine Folge von Ganzzahlen wird über die Tastatur eingelesen
        ' Sobald eine Zahl falsch ist, wird ein Ereignis ausgelöst
        Console.Out.Write("Nombre entier (rien pour arrêter) : ")
        Dim saisie As String = Console.In.ReadLine().Trim()
        ' solange die eingegebene Zeile nicht leer ist
        While saisie <> ""
            ' Ist die Eingabe eine ganze Zahl?
            Try
                Dim n As Integer = Integer.Parse(saisie)
            Catch
                ' Es handelt sich nicht um eine ganze Zahl
                ' Alle werden benachrichtigt
                émetteur1.envoyerEvt(New myEventArgs(saisie))
            End Try
            ' alle werden benachrichtigt
            ' Neue Eingabe
            Console.Out.Write("Nombre entier (rien pour arrêter) : ")
            saisie = Console.In.ReadLine().Trim()
        End While
        ' Ende
        Environment.Exit(0)
    End Sub
End Class

Der obige Code ist recht komplex. Schauen wir uns das genauer an. In einem Ereignismanagement gibt es einen Ereignissender (sender), der die Ereignisdetails (EventArgs) an Abonnenten sendet, die ihr Interesse an den betreffenden Ereignissen bekundet haben. Die sendende Klasse der Ereignisse lautet hier wie folgt:


Public Class émetteur
    ' die Klasse, die ein Ereignis auslöst
    ' Attribut
    Private _nom As String    ' nom de l'émetteur

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

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

    ' der Prototyp der Funktionen zur Verarbeitung des Ereignisses
    Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)

    ' der Pool der Ereignisbehandler
    Public Event evtHandler As _evtHandler

    ' Methode zur Anforderung der Auslösung eines Ereignisses
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' Alle Abonnenten werden benachrichtigt
        ' Es wird so getan, als stamme das Ereignis von hier
        RaiseEvent evtHandler(Me, evt)
    End Sub
End Class

Jedes Objekt vom Typ émetteur verfügt konstruktionsbedingt über ein nom. Die Methode ToString wurde so umdefiniert, dass bei der Umwandlung eines Objekts vom Typ émetteur in string, dessen Name zurückgegeben wird. Die Klasse definiert ein Ereignis:


        ' Der Prototyp der Funktionen zur Verarbeitung des Ereignisses
        Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)

        ' das Ereignis
        Public Event evtHandler As _evtHandler

Das erste Argument einer Funktion zur Verarbeitung eines Ereignisses ist das Objekt, das das Ereignis auslöst. Das zweite Argument ist vom Typ myEventArgs, ein Objekt, das Details zum Ereignis liefert und auf das wir später noch zurückkommen werden. Um ein Ereignis von außerhalb eines Objekts émetteur auslösen zu können, fügen wir der Klasse die Methode envoyerEvt hinzu:


    ' Methode zum Anfordern der Auslösung eines Ereignisses
    Public Sub envoyerEvt(ByVal evt As myEventArgs)
        ' Alle Abonnenten werden benachrichtigt
        ' Es wird so getan, als stamme das Ereignis von hier
        RaiseEvent evtHandler(Me, evt)
    End Sub

Wir müssen festlegen, welche Art von Ereignissen durch die Klasse émetteur ausgelöst werden sollen. Zu diesem Zweck haben wir die Klasse myEventArgs definiert:


Public Class myEventArgs
    ' die Klasse eines Ereignisses
    ' Attribut
    Private _saisie As String

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

    ' schreibgeschützte Eigenschaft
    Public Overrides Function ToString() As String
        Return _saisie
    End Function
End Class

Die Ereignisse, die für uns von Interesse sind, 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, wird ein Ereignis ausgelöst. Als Detail des Ereignisses geben wir lediglich die fehlerhafte Eingabe an. Dies ist die Bedeutung des Attributs _saisie der Klasse. Ein Objekt myEventArgs wird daher mit der fehlerhaften Eingabe als Parameter erstellt. Außerdem definieren wir die Methode ToString neu, damit bei der Umwandlung eines Objekts vom Typ myEventArgs in eine Zeichenkette das Attribut _saisie erhalten wird.

Wir haben den Absender der Ereignisse und die Art der von ihm gesendeten Ereignisse definiert. Anschließend definieren wir den Typ der Abonnenten, die an diesen Ereignissen interessiert sind.


    ' eine Ereignisbehandlungs-Klasse
    Public Class souscripteur
        ' Attribut
        Private nom As String     ' nom du souscripteur
        Private sender As émetteur     ' l'émetteur des évts

        ' Hersteller
        Public Sub New(ByVal nom As String, ByVal e As émetteur)
            ' hier wird der Name des Zeichners vermerkt
            Me.nom = nom
            ' sowie den Absender der Ereignisse
            Me.sender = e
            ' man abonniert die Ereignisse des Emittenten e
            AddHandler e.evtHandler, AddressOf traitementEvt
        End Sub

        ' Ereignismanager
        Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
            ' Ereignisanzeige
            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 nom) und das Objekt émetteur, dessen Ereignisse er verarbeiten möchte (Attribut sender). Diese beiden Parameter werden an den Konstruktor des Objekts übergeben. Im Rahmen dieser Erstellung abonniert der Abonnent die Ereignisse des Senders:


            ' Man registriert sich, um die Ereignisse des Senders e zu empfangen
            AddHandler e.evtHandler, AddressOf traitementEvt

Die beim Sender registrierte Funktion lautet traitementEvt. Diese Methode der Klasse souscripteur gibt die beiden empfangenen Argumente (sender, evt) sowie den Namen des Empfängers (nom) aus. Der Typ der erzeugten Ereignisse, der Typ des Absenders dieser Ereignisse und der Typ der Abonnenten wurden definiert. Nun müssen wir sie nur noch implementieren:


    ' ein Testprogramm
    Public Class test
        Public Shared Sub Main()
            ' Erstellung eines Ereignis-Senders
            Dim émetteur1 As New émetteur("émetteur1")
            ' Erstellung einer Abonnentenliste
            ' für die vom Sender1 gesendeten Ereignisse
            Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
            ' Eine Folge von Ganzzahlen wird über die Tastatur eingelesen
            ' Sobald eine Zahl falsch ist, wird ein Ereignis ausgelöst
            Console.Out.Write("Nombre entier (rien pour arrêter) : ")
            Dim saisie As String = Console.In.ReadLine().Trim()
            ' solange die eingegebene Zeile nicht leer ist
            While saisie <> ""
                ' Ist die Eingabe eine ganze Zahl?
                Try
                    Dim n As Integer = Integer.Parse(saisie)
                Catch
                    ' Es handelt sich nicht um eine ganze Zahl
                    ' Alle werden benachrichtigt
                    émetteur1.envoyerEvt(New myEventArgs(saisie))
                End Try
                ' alle werden benachrichtigt
                ' Neue Eingabe
                Console.Out.Write("Nombre entier (rien pour arrêter) : ")
                saisie = Console.In.ReadLine().Trim()
            End While
            ' Ende
            Environment.Exit(0)
        End Sub

Wir erstellen ein Objekt émetteur:


        ' Erstellung eines Ereignis-Senders
        Dim émetteur1 As New émetteur("émetteur1")

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


        ' Erstellung eines Abonnenten-Arrays
        ' für die von Sender1 gesendeten Ereignisse
        Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}

Wir fordern den Benutzer auf, ganze Zahlen über die Tastatur einzugeben. Sobald eine Eingabe fehlerhaft ist, weisen wir émetteur1 an, ein Ereignis an seine Abonnenten zu senden:


                ' alle werden benachrichtigt
                é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 zeigt die folgende Ausführung.

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