6. 事件管理
上一章我们讨论了与组件相关的事件概念。现在我们将了解如何在自己的类中创建事件。这是一个较为复杂的概念,初学者可能不感兴趣,建议直接跳至下一章。
6.1. 委托对象
语句
定义了一个名为 opération 的类型,它实际上是一个接受两个整数并返回一个整数的函数原型。正是关键字 delegate 使 opération 成为函数原型的定义。 类型 [opération] 可以按以下方式赋值给变量:
Dim op As New opération(AddressOf [fonction])
类型为 [délégué] 的变量作为对象进行构造。构造函数的参数是与委托实例关联的函数。 在 VB 中,该函数以 AddressOf 函数的形式给出,其中 [fonction] 是函数的名称。一旦创建了委托的实例,它就可以像函数一样使用:
' 执行委托函数
Dim n As Integer = op(4, 7)
委托 [opération] 被定义为接收两个类型为 [integer] 的参数,并返回同类型的结果。因此,上文中的实例 [op] 被用作此类函数。请看以下示例:
' 委托函数
Imports System
Public Class delegate1
' 定义函数原型
' 接受 2 个整数作为参数并返回一个整数
Delegate Function opération(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
' 与原型对应的两个实例方法
' 加
Public Function ajouter(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
Console.Out.WriteLine(("ajouter(" & n1 & "," & n2 & ")"))
Return n1 + n2
End Function
' 减法
Public Function soustraire(ByVal n1 As Integer, ByVal n2 As Integer) As Integer
Console.Out.WriteLine(("soustraire(" & n1 & "," & n2 & ")"))
Return n1 - n2
End Function
' 一个与原型对应的静态方法
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
' 测试程序
Public Shared Sub Main()
' 定义一个操作类型的对象来存储函数
' 注册静态函数“增加”
Dim op As New opération(AddressOf delegate1.augmenter)
' 执行委托
Dim n As Integer = op(4, 7)
Console.Out.WriteLine(("n=" & n))
' 创建类型为 class1 的对象 c1
Dim c1 As New delegate1
' 将 c1 的 add 方法注册到委托中
op = New delegate1.opération(AddressOf c1.ajouter)
' 执行委托对象
n = op(2, 3)
Console.Out.WriteLine(("n=" & n))
' 将 c1 的减法方法注册到委托中
op = New delegate1.opération(AddressOf c1.soustraire)
n = op(2, 3)
Console.Out.WriteLine(("n=" & n))
End Sub
End Class
执行结果如下:
6.2. 事件管理
delegate 类型的对象有什么用途?
正如我们在下面的示例中所见,这主要用于事件管理。一个名为 C1 的类可以生成 evti 类型的事件。 在发生 evti 事件时,一个 C1 类型的对象将触发一个 evtiDéclenché 类型的对象(其类型为 delegate)的执行。 此时,委托对象 evtiDéclenché 中注册的所有函数都将被执行。 如果一个使用 C1 对象的 C2 对象希望在 C1 对象上发生 evti 事件时收到通知, 它将把其方法 C2.f 之一注册到对象 C1 的委托对象 C1.evtiDéclenché 中,以便每当evti 事件发生在 C1 对象上时,该方法 C2.f 就会被执行。由于委托对象 C1.evtiDéclenché 可以注册多个函数, 不同的 Ci 对象可以向代理 C1.evtiDéclenché 注册,以便在 C1 上发生 evti 事件时收到通知。
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] 语句:
- event:要关联新事件处理程序的事件变量
- eventhandler:事件处理程序的名称——必须具有与该事件关联的 [délégué] 的签名
一个事件可以关联任意数量的处理程序。当关联的事件被触发时,所有处理程序都将被执行。
6.2.3. 触发事件
要通过程序触发事件,需使用指令 [RaiseEvent]:
- eventname:事件变量(使用关键字 Event 声明)
- argumentlist:与该事件关联的委托函数的参数。
所有通过指令 AddHandler 与该事件关联的处理程序都将被调用。它们将接收事件签名中定义的参数作为参数。
6.2.4. 示例
请看以下示例:
'事件处理
Imports System
Public Class myEventArgs
Inherits EventArgs
' 事件的类
' 属性
Private _saisie As String
' 构造函数
Public Sub New(ByVal saisie As String)
_saisie = saisie
End Sub
' 只读属性
Public Overrides Function ToString() As String
Return _saisie
End Function
End Class
Public Class émetteur
' 事件的触发类
' 属性
Private _nom As String ' nom de l'émetteur
' 构造函数
Public Sub New(ByVal nom As String)
_nom = nom
End Sub
' ToString
Public Overrides Function ToString() As String
Return _nom
End Function
' 负责处理该事件的函数原型
Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
' 事件处理程序池
Public Event evtHandler As _evtHandler
' 请求触发事件的方法
Public Sub envoyerEvt(ByVal evt As myEventArgs)
' 通知所有订阅者
' 模拟事件源自此处
RaiseEvent evtHandler(Me, evt)
End Sub
End Class
' 事件处理类
Public Class souscripteur
' 属性
Private nom As String ' nom du souscripteur
Private sender As émetteur ' l'émetteur des évts
' 构造函数
Public Sub New(ByVal nom As String, ByVal e As émetteur)
' 记录订阅者的名称
Me.nom = nom
' 以及事件的发布者
Me.sender = e
' 订阅以接收来自发布者的事件
AddHandler e.evtHandler, AddressOf traitementEvt
End Sub
' 事件管理器
Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
' 事件显示
Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
End Sub
End Class
' 一个测试程序
Public Class test
Public Shared Sub Main()
' 创建事件发送器
Dim émetteur1 As New émetteur("émetteur1")
' 创建订阅者数组
' 用于订阅器1发出的事件
Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
' 从键盘读取一组整数
' 一旦其中一个有误,就触发一个事件
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
Dim saisie As String = Console.In.ReadLine().Trim()
' 只要输入的行不为空
While saisie <> ""
' 输入是否为整数?
Try
Dim n As Integer = Integer.Parse(saisie)
Catch
' 这不是一个整数
' 通知所有人
émetteur1.envoyerEvt(New myEventArgs(saisie))
End Try
' 通知所有人
' 重新输入
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
saisie = Console.In.ReadLine().Trim()
End While
' 结束
Environment.Exit(0)
End Sub
End Class
上述代码相当复杂。 让我们详细分析一下。在事件管理中,有一个事件发布者(sender),它将事件详情(EventArgs)发送给已声明对相关事件感兴趣的订阅者。此处的事件发布类如下:
Public Class émetteur
' 事件的触发类
' 属性
Private _nom As String ' nom de l'émetteur
' 构造函数
Public Sub New(ByVal nom As String)
_nom = nom
End Sub
' ToString
Public Overrides Function ToString() As String
Return _nom
End Function
' 负责处理该事件的函数原型
Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
' 事件处理程序池
Public Event evtHandler As _evtHandler
' 请求触发事件的方法
Public Sub envoyerEvt(ByVal evt As myEventArgs)
' 通知所有订阅者
' 将事件伪装成来自此处
RaiseEvent evtHandler(Me, evt)
End Sub
End Class
每个 émetteur 对象在构造时都会被分配一个固定的 nom。 方法 ToString 已被重新定义,使得在将 émetteur 对象转换为 string, 时,将获取其名称。该类定义了一个事件:
' 负责处理事件的函数原型
Delegate Sub _evtHandler(ByVal sender As Object, ByVal evt As myEventArgs)
' 事件
Public Event evtHandler As _evtHandler
事件处理函数的第一个参数将是触发该事件的对象。 第二个参数的类型为 myEventArgs,这是一个提供事件详细信息的对象,我们稍后将详细讨论。为了能够从 émetteur 对象外部触发事件,我们在类中添加了方法 envoyerEvt:
' 触发事件的方法
Public Sub envoyerEvt(ByVal evt As myEventArgs)
' 通知所有订阅者
' 将事件伪装成来自此处
RaiseEvent evtHandler(Me, evt)
End Sub
我们需要确定由类 émetteur 触发的事件类型。为此,我们定义了类 myEventArgs:
Public Class myEventArgs
' 事件的类
' 属性
Private _saisie As String
' 构造函数
Public Sub New(ByVal saisie As String)
_saisie = saisie
End Sub
' 只读属性
Public Overrides Function ToString() As String
Return _saisie
End Function
End Class
我们需要关注的事件是键盘输入错误。我们将要求用户通过键盘输入整数,一旦输入的字符串不代表整数,就会触发一个事件。 作为事件的详细信息,我们将仅提供错误输入内容。这就是该类中 _saisie 属性的作用。因此,会创建一个 myEventArgs 对象,其参数即为该错误输入。 此外,我们还重写了 ToString 方法,以便在将 myEventArgs 对象转换为字符串时,能获得 _saisie 属性。
我们已定义了事件发布者及其发布的事件类型。接下来,我们将定义对这些事件感兴趣的订阅者的类型。
' 事件处理类
Public Class souscripteur
' 属性
Private nom As String ' nom du souscripteur
Private sender As émetteur ' l'émetteur des évts
' 制造商
Public Sub New(ByVal nom As String, ByVal e As émetteur)
' 注明投保人姓名
Me.nom = nom
' 以及事件发布者
Me.sender = e
' 订阅以接收来自事件发布者的事件
AddHandler e.evtHandler, AddressOf traitementEvt
End Sub
' 事件管理器
Public Sub traitementEvt(ByVal sender As Object, ByVal evt As myEventArgs)
' 事件显示
Console.Out.WriteLine(("L'objet [" + sender.ToString + "] a signalé la saisie erronée [" + evt.ToString + "] au souscripteur [" + nom + "]"))
End Sub
End Class
一个订阅者将由两个参数定义:其名称(属性 nom)以及它希望处理其事件的对象 émetteur(属性 sender)。这两个参数将传递给对象的构造函数。 在该构造过程中,订阅者将订阅发布者的事件:
' 注册以接收来自发布者 e 的事件
AddHandler e.evtHandler, AddressOf traitementEvt
在发布者处注册的函数是 traitementEvt。 souscripteur 类的此方法会显示其接收的两个参数(sender、evt)以及接收器的名称(nom)。 已定义了生成的事件类型、这些事件的发送者类型以及订阅者的类型。现在我们只需将其实现:
' 一个测试程序
Public Class test
Public Shared Sub Main()
' 创建事件发布者
Dim émetteur1 As New émetteur("émetteur1")
' 创建订阅者数组
' 用于接收发布者1发出的事件
Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
' 从键盘读取一组整数
' 一旦其中一个有误,就触发一个事件
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
Dim saisie As String = Console.In.ReadLine().Trim()
' 只要输入的行不为空
While saisie <> ""
' 输入是否为整数?
Try
Dim n As Integer = Integer.Parse(saisie)
Catch
' 这不是一个整数
' 通知所有人
émetteur1.envoyerEvt(New myEventArgs(saisie))
End Try
' 通知所有人
' 重新输入
Console.Out.Write("Nombre entier (rien pour arrêter) : ")
saisie = Console.In.ReadLine().Trim()
End While
' 结束
Environment.Exit(0)
End Sub
我们创建一个 émetteur 对象:
' 创建事件发送器
Dim émetteur1 As New émetteur("émetteur1")
我们为对象 émetteur1 发出的事件创建一个包含两个订阅者的数组:
' 创建订阅者数组
' 针对发布者1发出的事件
Dim souscripteurs() As souscripteur = {New souscripteur("s1", émetteur1), New souscripteur("s2", émetteur1)}
我们要求用户通过键盘输入整数。一旦输入错误,我们就要求 émetteur1 向其订阅者发送一个事件:
' 通知所有人
é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) :