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