“感兴趣就好!”大李微微点点头,然后问我道,“你知道程序是怎么知道哪些对象可以引发何种事件,以及怎么样把事件和事件处理程序关联起来?”
见到我迷茫的眼神,大李用鼠标点击打开被Region合起来的“Windows 窗体设计器生成的代码”,指着中间的一句自动生成的代码:
Friend WithEvents Button1 As System.Windows.Forms.Button
“当你在设计窗口往窗体上添加了一个按钮后,会自动在代码中加入这句代码,Friend 关键字授予对一个或多个所声明的编程元素的友元访问权限。那么WithEvent是干什么的?”
我立刻在帮助中查找,有了:
WithEvents 关键字指示被声明的对象变量引用可以引发事件的类实例。
“明白了,”我也不能总傻听着,“在声明对象的时候用WithEvents来进行标识,再在事件处理程序中的过程声明结尾处使用 Handles 关键字将就可以处理由使用 WithEvents 关键字声明的对象变量所引发的事件了。”
“没错,你看来我写一段代码,使用的就是WithEvent-Handles的方法来处理事件。类名为CHenry吧,它内含一个事件EventHR。”
Module Module1
Public Class CHenry
Public Event EventHR() ' 声明一个事件
Sub CauseSomeEvent()
RaiseEvent EventHR() ' 引发事件
End Sub
End Class
WithEvents Obj As New CHenry() '模块或类级别的声明
Sub Obj_EventHR() Handles Obj.EventHR '在Handles之后声明事件
MsgBox("事件处理器捕捉到了事件.") '处理事件.
End Sub
Sub Main()
Obj.CauseSomeEvent() '调用对象去引发事件
End Sub
End Module
“你要注意的是怎么样通过RaiseEvent来引发事件,另外一个很简单的问题也需要注意的是,事件处理程序的命名一般是用‘对象名_事件名’的方式。”大李边写程序边指点说。
“事件的声明和引发是只能在一个层次内,还是可以在派生类里引发基类的事件?”我不由好奇地问。
“你开始学会思考了,”大李不知道是不是在嘲笑我,“VB.NET要求必须在声明事件的范围内引发事件。派生类不能引发从基类继承的事件,但是可以处理基类引发的事件。我们可以来看一个示例。”大李马上对刚写的代码进行了修改:
Module Module1
Public Class CHenry
Public Event EventHR() ' 声明一个事件
Sub CauseSomeEvent()
RaiseEvent EventHR() ' 引发事件
End Sub
End Class
Public Class Class2 ‘从Chenry派生而来的类
Inherits CHenry
Sub Obj_EventHR() Handles MyBase.EventHR
MsgBox("事件处理器捕捉到了事件.") '处理事件.
End Sub
End Class
WithEvents Obj As New Class2() '模块或类级别的声明
Sub Main()
Obj.CauseSomeEvent() '调用对象去引发事件
End Sub
End Module
“还记得我跟你说过MyBase的意义了吧?(注:详见《构造与析构》一篇)我们可以添加 Handles MyBase.<event name> 语句来声明派生类中的事件处理程序。问一个基本的问题,你看obj是Class2类的一个实例对吧?但为什么它也具有Chenry类才有的CauseSomeEvent方法?”大李说得我眼球都快掉下来了。
“当然是因为Class2是CHenry类的派生类,继承了它的方法呗。”我神情极为沮丧。
“哈哈,别急呀,在VB.NET中我们最常碰到的就是面向对象的问题。不断回顾有好处,温故而知新!”大李最后的微笑对我倒不啻为一句忠告。要学好VB.NET,我还真得好好复习一下面向对象的内容。
大李话题一转,淡淡地跟我说:“WithEvents 语句和 Handles 子句提供了标准的陈述性指定事件处理程序的方法。也就是如何把对象的事件和某一个事件处理程序进行关联。WithEvents 所声明对象引发的事件可以由任何过程用命名此事件的 Handles 子句来处理。换句话说,有Handles子句标识的事件处理程序也只能处理由WithEvents声明的对象。虽然 Handles 子句是关联事件与事件处理程序的标准方法,它仅限于在编译时关联事件与事件处理程序。还有一种方法可以允许在运行时动态地将事件与一个或更多的事件处理程序连接或者断开,而并不要求使用 WithEvents 来声明对象变量。”
“是吗?”我一下子从沉思中惊醒过来,好奇心又一次袭来。
Henry的VB.NET之旅(十四)—动态关联事件与处理程序
韩睿
“要解释新的事件处理程序的方法,我们需要先说几个重要的问题。”大李开始严肃起来,我也只得挪动一下身子,表现出正襟危坐的架势。
“我们首先讨论一下事件是怎么产生的。事件是对象发送的消息,以发信号通知操作的发生。操作可能是由用户交互,例如鼠标单击引起的,也可能是由某些其他的程序逻辑触发的。引发事件的对象叫做事件发送方(啊,听到这,Henry突然明白了事件处理程序中的第一个参量为什么叫Sender了,就是指事件发送的那个对象呀)。捕获事件并对其作出响应的对象叫做事件接收方。在事件通讯中,事件发送方类不知道哪个对象或方法将接收到它引发的事件。所需要的是在源和接收方之间存在一个媒介,或类似指针的机制。.NET 框架定义了一个特殊的类型Delegate,也就是委托,该类型提供函数指针的功能。”
“啊,我早就听说过委托的,就是不知道它是什么意思咧!”我做出期盼状。
“委托就是可用于调用其他对象方法的对象。与其他的类不同,委托类具有一个签名,并且它只能对与其签名匹配的方法进行引用。”大李继续说着。
““噢,委托也就是一个函数指针喽。”我好象理解了一点。
“差不多,委托可以等效于一个类型安全函数指针或一个回调。但不同于函数指针,Visual Basic.NET 委托是基于 System.Delegate 类的引用类型,它可以引用我们先前说过的共享方法(详见《共享成员》一篇)和实例方法。”
“明白了一点。”我一边点头一边说,“我们想要动态调用事件处理程序,是不是就要利用委托来声明是哪个程序用于处理事件呀?”
大李惊异地看着我,露出了几分赞许。
“委托是.NET中的一个重要的类型,我们以后还需要详细讨论。现在我们需要关注的就是如何来通过操作委托来实现将事件与事件处理程序动态联系起来。”大李接着就开始修改代码:
Module Module1
Public Class CHenry
Public Event EventHR() ' 声明一个事件
Sub CauseSomeEvent()
RaiseEvent EventHR() ' 引发事件
End Sub
End Class
Dim obj As New CHenry()
Sub Obj_EventHR() '在Handles之后声明事件
MsgBox("事件处理器捕捉到了事件.") '处理事件.
End Sub
Sub Main()
AddHandler obj.EventHR,AddressOf Obj_EventHR
obj.CauseSomeEvent() '调用对象去引发事件
End Sub
End Module
“看到有什么不同吗?”大李转过身来问我。
“主要有两个不同,首先是obj的定义不再用WithEvents来标识了,因此事件处理程序obj_EventHR()也就不能通过Handles关键字来声明事件,也就是说EventHR事件与事件处理程序没有用WithEvent-Handles进行关联;其次,是使用了AddHandle和Addressof……”说到这,我也没有词了,只能语焉不详。
“呵呵,我来帮你接着说。”大李拍了拍我的肩膀,接着说“先说Addressof吧,AddressOf 运算符创建的是一个指向指定的过程的过程委托。我们刚才说过委托相当于一个函数指针,那么AddressOf就是委托的操作符,通过它能得到委托的引用。”
见到我稍稍明白了,大李又接着说:“光看AddHandle能够将obj.EventHR事件与Obj_EventHR事件处理程序关联起来的作法,你肯定不了解我所说的动态关联好处在哪里。因为我没提到另一个方法RemoveHandler。它的使用方法和AddHandle是一样的,比如:
RemoveHandler obj.EventHR,AddressOf Obj_EventHR
你看,AddHandler 与 RemoveHandler 在一起就可以提供比 Handles 子句更大的灵活性,只要我们善于利用它们,就可以动态地添加、移除和更改与某事件关联的事件处理程序。而且比 Handles 要强大的是,AddHandler 允许将多个事件处理程序与单个事件进行关联。”
大李停了一停,接着说:“你要注意的一点就是AddressOf后面跟着的委托签名应该与相应的事件数据类相一致,我们看一个例子。”
AddHandler TextBox.MouseDown,AddressOf TextBoxMouseDownHandler
‘错误的示例1:
Private Sub TextBoxMouseDownHandler( )
End Sub
‘错误的示例2
Private Sub TextBoxMouseDownHandler(ByVal sender As Object,ByVal e As EventArgs)
End Sub
‘正确的示例:
Private Sub TextBoxMouseDownHandler(ByVal sender As Object,ByVal e As MouseEventArgs)
End Sub
“事件是一个文本框中的鼠标按下事件,我们不用自带的标准关联事件处理方法,而用AddHandler来实现,那么AddressOf之后相应的方法的参数声明,应该与MouseDown事件对应的事件的委托MouseEventhandler具有相同的签名,也就是参数声明上要保持一致,一个object变量,一个System.Windows.Forms.MouseEventArgs变量。”
我到这一步才算明白了个大概,事件处理程序可以通过AddHandler和RemoveHandler方法在我们需要的时候动态地建立或断开事件与事件处理程序的关联关系。可是,对于大李刚说的“事件的委托MouseEventhandler”,我还是不太理解。