Skip to content

6. 事件处理

在上一章中,我们讨论了与组件相关的事件概念。现在我们将了解如何在自己的类中创建事件。这是一个较为复杂的概念,初学者可能不感兴趣,建议直接跳至下一章。

6.1. 委托对象

语句

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

定义了一个名为 operation 的类型,它实际上是一个函数原型,接受两个整数并返回一个整数。正是 delegate 关键字使 operation 成为函数原型定义。[operation] 类型可以如下赋值给变量:


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

类型为 [delegate] 的变量被构造为一个对象。构造函数的参数是与该委托实例关联的函数。在 VB 中,该函数指定为 AddressOf function,其中 [function] 是函数的名称。一旦创建了委托实例,它就可以作为函数使用:


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

该委托 [operation] 被定义为接受两个 [integer] 类型的参数,并返回相同类型的结果。因此,上文中的实例 [op] 被用作此类函数。请看以下示例:


' 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

执行结果如下:

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

6.2. 事件处理

委托对象可以用于什么?

正如我们在下面的示例中所见,它主要用于事件处理。类 C1 可以生成 evti 事件。当发生 evti 事件时,类型为 C1 的对象将触发类型为委托的 evtiDéclenché 对象的执行。 随后,evtiDéclenché 委托对象中注册的所有函数都将被执行。如果使用对象 C1 的对象 C2 希望在对象 C1 上发生 evti 事件时收到通知,它将把其方法之一 C2.f 注册到对象 C1 的委托对象 C1.evtiDéclenché 中,这样每当对象 C1 上发生 evti 事件时,方法 C2.f 就会被执行。 由于委托对象 C1.evtiDéclenché 可以注册多个函数,因此不同的对象 Ci 均可向委托 C1.evtiDéclenché 注册,以接收关于 C1evti 事件的通知。

6.2.1. 声明事件

事件的声明如下 (1):

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

或 (2)

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

关键字 [Event] 将一段数据定义为事件。该事件与其处理程序必须具备的签名相关联。该签名可以通过委托(方法 1)或直接(方法 2)进行定义。

6.2.2. 定义事件处理程序

当 [RaiseEvent] 语句触发事件时,必须对其进行处理。负责处理事件的程序称为事件处理程序。创建 [Event] 对象时,仅声明事件处理程序的签名。该签名即与事件关联的 [delegate] 对象的签名。 完成此步骤后,即可将处理程序与事件关联。这些处理程序必须与事件关联的 [delegate] 具有相同的签名。要将事件处理程序与事件关联,请使用 [AddHandler] 语句,语法如下:

AddHandler event, AddressOf eventhandler
  • event:要关联新事件处理程序的事件变量
  • eventhandler:事件处理程序的名称——其签名必须与该事件关联的 [委托] 相同

您可以为一个事件关联任意数量的处理程序。当关联的事件被触发时,所有处理程序都将被执行。

6.2.3. 触发事件

要通过编程方式触发事件,请使用 [RaiseEvent] 语句:

RaiseEvent eventname[( argumentlist )]
  • eventname:事件变量(使用 Event 关键字声明)
  • 参数列表:与该事件关联的委托的参数。

所有使用 AddHandler 语句与该事件关联的处理程序都将被调用。它们将接收事件签名中定义的参数作为参数。

6.2.4. 示例

请看以下示例:


'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

上面的代码相当复杂。让我们来分解一下。在事件处理中,有一个事件发送者,它会将事件详细信息(EventArgs)发送给对相关事件感兴趣的订阅者。此处的事件发送类如下:


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

每个发射器对象都有一个默认名称。已重定义 ToString 方法,因此当发射器对象转换为字符串时,将返回其名称。该类定义了一个事件:


        ' 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

事件处理函数的第一个参数将是触发该事件的对象。第二个参数的类型为 myEventArgs,这是一个提供事件详细信息的对象,我们稍后将对此进行讨论。若要在触发器对象外部触发事件,我们需在类中添加 envoyerEvt 方法:


    ' 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

我们需要定义发射器类触发的事件类型。为此,我们定义了 myEventArgs 类:


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

我们关注的是错误的键盘输入。我们将要求用户在键盘上输入整数,一旦他们输入的字符串不代表整数,就会触发一个事件。作为事件详情,我们将直接提供该错误输入。这就是该类中 _input 属性的作用。因此,会创建一个 myEventArgs 对象,并将错误输入作为其参数。 我们还重写了 ToString 方法,以便在将 myEventArgs 对象转换为字符串时,能够获取 _input 属性。

我们已经定义了事件发射器及其发出的事件类型。接下来,我们将定义对这些事件感兴趣的订阅者的类型。


    ' 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

订阅者由两个参数定义:其名称(name 属性)以及它希望处理其事件的发射器对象(sender 属性)。这两个参数会被传递给对象的构造函数。在构造过程中,订阅者会订阅发射器的事件:


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

向发布者注册的函数是 traitementEvt。订阅者类的此方法会显示其接收到的两个参数(senderevt)以及接收者的名称(nom)。已定义了生成的事件类型、这些事件发布者的类型以及订阅者的类型。剩下的就是实现它们:


    ' 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

我们创建一个事件发射器对象:


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

我们为émetteur1对象发出的事件创建了一个包含两个订阅者的数组:


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

我们要求用户通过键盘输入整数。一旦输入错误,我们就要求 issuer1 向其订阅者发送一个事件:


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

发送的事件类型为 myEventArgs,其中包含错误的输入。两个订阅者都应收到此事件并进行报告。如下面的执行所示。

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