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
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:
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):
oder (2)
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:
- 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]:
- 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) :