|
|
|
|
动态添加和移除处理程序 到目前为止你所看到的每个事件侦听器都需要你在设计时关联到事件处理程序。Handles子句是关联使用WithEvents关键字的对象引发事件的方便而简单的方法,但是它不能在运行时提供任何弹性。另外,当多个过程处理相同的事件,Handles子句不会给你事件处理程序执行顺序的控制权。(当然,为了避免这个问题你可以使用你前面看到的技巧,遍历调用GetInvocationList返回项。这个技术要求引发事件的类的附加代码,不是在关联事件侦听器的代码中。) 为了在何时及以什么顺序调用事件处理程序上获得完全主动,你可以使用AddHandler(和RemoveHandler)语句而不是Handles子句。AddHandler和RemoveHandler语句允许你提供一个特定的事件和准备响应事件被调用过程的地址。每个对AddHandler的调用使过程和事件相关联以使得.NET Framework在事件发生时调用过程。另外,AddHandler总是添加事件处理程序到事件调用列表的末尾。这意味着你控制了事件被处理的顺序。 当然,如果你再多考虑片刻,你会理解当你有多个过程时,相同的事件都有一个Handles子句,Visual Basic编译器为事件处理程序创建一个多路广播委托实例而不允许你控制它们被添加进的顺序。引发的事件调用委托实例的Invoke方法,这时就按每个事件侦听器被添加的顺序(并且你无权控制这个顺序)调用它们。当你使用AddHandler和RemoveHandler语句而不是Handles子句,你只要简单控制各项加入多路广播委托的顺序。每次你的应用程序对相同事件调用AddHandler语句,你就为这个事件添加了一个新的侦听器到列表的最后。当你引发这个事件,.NET运行时按顺序调用每个侦听器。 如果你点击了示例窗体的Add/RemoveHandler按钮,一个新的FileSearch5类的实例被创建,同时实例的FileFound事件的多个事件处理程序被整合(hooked up)。这时,当代码调用实例的Execute方法,示例窗体的listbox控件显示出结果: ’’ 来自 frmMain.vb然后,代码将EventHandler7从回调列表中移去并再次调用execute方法: RemoveHandler fs5.FileFound, AddressOf EventHandler7最后,代码移除剩余的事件处理程序: RemoveHandler fs5.FileFound, AddressOf EventHandler6记住,在你调用AddHandler和RemoveHandler语句时你提供地址的过程必须有正确的委托类型。因此,除非你提供给AddHandler 和 RemoveHandler地址的过程参数签名与事件的参数相符(就是说,除非它们有正确的委托的类型),否则你的代码将不能被编译。 ![]() 图9 控制事件处理程序回调的顺序 图 9显示了点击Add/RemoveHandler按钮后的显示。正如你所能看到的,事件过程按你将它们添加到回调列表的顺序被调用。 Visual Basic中的自定义事件 还记得在一个事件有多个侦听器时发生的问题,还有一个事件侦听器抛出一个异常吗?这个问题有一个相当简单的解决方案,用多路广播委托实例的回调列表关联到这个事件。然而,在Visual Basic .NET 2002 和2003,这里有一些其他的事件挑战:(如果)没有复杂或低效率的代码,简单(的方法)将无法体现。Visual Basic 2005以前,事件委托类型的实例总是由Visual Basic编译器为你创建,并且编译器无法提供给你修改这个委托实例的行为。 Visual Basic 2005为事件声明添加了新的Custom关键字。这个关键字允许你为事件的AddHandler, RemoveHandler和RaiseEvent行为提供代码。 这取决于你创建适当的委托类型及创建拥有关于事件侦听器信息的类型的实例。然而,除此之外,你有对你如何处理事件有着完全地控制。 为了在Visual Studio 2005中创建一个自定义事件,在一个类里面放入你的游标,并为你的事件录入一个声明,如下: Public Custom Event <YourEventName> As <EventDelegateType>当你完成这行代码时,编辑器将插入关联的AddHandler, RemoveHandler和RaiseEvent部分。比如,设想一下,你希望通过创建一个自定义事件处理程序来解决异常问题。示例项目的Visual Basic 2005版的FileSearch6类包含像这样做的一个自定义FileFound事件。代码包括适当的事件委托的一个声明,如下所示: Public Delegate Sub FileFoundEventHandler( _键入事件声明添加一个空的自定义事件,如图 10 所示的。 Public Custom Event FileFound As FileFoundEventHandler 这要由你提供事件委托实例的存储(storage),并提供储存,移出并回调事件侦听器的代码。对于本例,因为代码需要能单独回调每个侦听器并捕捉及处理任何异常,FileSearch6类包括了一个储存了FileFoundEventHandler实例的泛型列表集合(generic List collection)。每次任何类为这个事件调用AddHandler,或用一个Handles子句捕捉这个事件,Visual Basic运行时引擎调用FileFound 自定义事件的AddHandler部分。代码必须添加FileFoundEventHandler以传递到泛型列表。RemoveHandler部分从内部集合里移去指定的委托实例。RaiseEvent部分调用每个委托实例的Invoke方法,捕捉和处理发生的异常。完全的自定义事件看起来如图 11 所示的代码。 Private listeners As New List(Of FileFoundEventHandler) 通过推进代码到事件声明本身,引发事件的代码不再需要担心处理异常的问题了。这就是说,与使用你前面看到的代码来引发事件不同,代码假定在Visual Basic 2005中的FileSearch6类可以简单地调用OnFileFound方法(它来引发事件)或直接调用RaiseEvent。担心异常的责任现在适得其所:它就在事件代码本身之中。这项技术直到Visual Basic 2005才可以使用。注意Visual Studio 2005现仍在beta版(测试中)。正因为如此,到最终版本发布前其细节会有变化。 还有其它可以用到自定义事件吗?Rocky Lhotka,一个Visual Basic MVP,在他的blog上包括另一个详细的例子( .NET 2.0 solution to serialization of objects that raise events )。他论述了 你可能会用到这个技术来解决涉及引发事件类序列化而侦听器没有序列化的问题。(令人惊讶的是,这经常发生,因为窗体没有序列化,但是常常用户创建事件的侦听器是序列化的。)Paul Vick,作为Microsoft的Visual Basic开发团队的成员之一,他的blog上包括一个例子显示你如何可以使用一个 自定义事件来减少暴露给大量事件而只有很少的事件可能会用到的类的系统开销。对于窗体来说就是这个情况,例如—窗体类暴露于大量事件,但是大多数时间里,你只会处理它们中一或两个。没有某些技巧,编译器将为每个事件引发一个委托实例,尽管你不会使用它们。请看Paul的blog在 Custom events。 |


