日期:2009-02-20  浏览次数:20381 次

本页内容
EventHandler 委托
自定义的事件参数
参数化自定义事件
小结

本月的内容是专门介绍事件编程的系列专栏(共三期)的最后一期。在前两期专栏中,我已经介绍了如何定义和引发事件(请参见 Basic Instincts:Programming with Events Using .NET 和 Basic Instincts:Static Event Binding Using WithEvents)。我还解释了如何使用动态和静态事件绑定来绑定事件处理程序。本月,我将通过一些在 Microsoft .NET Framework 中处理较常用的事件处理实例来总结我对事件的介绍。

EventHandler 委托


当您使用 Windows® 窗体或 ASP.NET 构建应用程序时,您会看到,在所遇到的事件中有相当大的比率是根据一个名为 EventHandler 的通用委托类型定义的。EventHandler 类型存在于 System 命名空间中并具有以下定义:

Delegate Sub EventHandler(sender As Object, e As EventArgs)

委托类型 EventHandler 在它的调用签名中定义了两个参数。第一个参数(名为 sender)是基于通用 Object 类型的。sender 参数用于传递指向事件源对象的引用。例如,当 Button 对象引发基于 EventHandler 委托类型的事件时,作为事件源的它将传递一个对自身的引用。

由 EventHandler 定义的第二个参数名为 e,它是 EventArgs 类型的对象。在许多情况下,事件源传递的参数值等于 EventArgs.Empty,这表明没有额外参数信息。如果事件源希望在 e 参数中传递额外的参数化信息,则它应该传递一个从 EventArgs 类的派生类创建的对象。

图 1 所示的示例在 Windows 窗体应用程序中包含了两个事件处理程序,它们使用静态事件绑定来绑定。Form 类的 Load 事件和 Button 类的 Click 事件都是根据委托类型 EventHandler 定义的。

您还应该注意到,图 1中的两个事件处理程序方法的名称和格式与 Visual Studio .NET IDE 为您生成的一致。例如,如果您在设计视图中双击某个窗体或命令按钮,Visual Studio .NET 将自动创建类似的事件处理程序方法主干。您需要做的仅仅是填充这些方法的实现,以便为您的事件处理程序赋予预期的行为。

您也许会注意到,Visual Studio .NET IDE 是使用 Visual Basic 6.0 要求的命名方案来生成处理程序方法的。然而,您应当记住的是,Visual Basic .NET 中的静态事件绑定并不真正与处理程序方法的名称有关。与其相关的是 Handles 子句。您可以随意将处理程序方法重命名为所需的任何名称。

您可以重写这两个事件处理程序,以便它们使用动态事件绑定(而非静态事件绑定)来绑定。例如,图 2 中从 Form 派生的类提供了与图 1中从 Form 派生的类完全相同的事件绑定行为。唯一的区别是,后者使用了动态事件绑定,并且不需要 WithEvents 关键字或 Handles 关键字。在许多情况下,您将根据 EventHandler 委托类型来编写处理程序方法的实现,而不是引用 sender 参数或 e 参数。例如,当您为从 Form 派生的类的 Load 事件编写处理程序时,这些参数值并没有实际的作用。sender 不会提供任何值,因为它只是传递 Me 引用。e 参数传递 EventArgs.Empty:

Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'*** these tests are always true
Dim test1 As Boolean = sender Is Me
Dim test2 As Boolean = e Is EventArgs.Empty
End Sub

您也许想知道,为什么 Load 事件的调用签名没有针对其需要进行更多自定义。毕竟,如果 Load 事件根本不包含任何参数,情况将不会这么令人困惑。要找到其他基于 EventHandler 委托类型的事件(并且其 sender 参数或 e 参数不传递任何值)的示例很容易。

请回答以下问题。如果该委托类型具有这样的通用调用签名,为什么您会认为有这么多事件根据 EventHandler 建模?.NET Framework 的设计者为什么不根据具有适合其需要的调用签名的自定义委托来为每个事件建模?如您所知,.NET Framework 开发中的一个设计目标就是限制用于事件处理的委托的数量。以下几条是更进一步的解释。

最小化委托类型数量的第一个目的是,为了更有效地利用应用程序所使用的内存。加载更多类型意味着占用更多内存。如果由 Windows 窗体框架中的类定义的每个事件都基于一个自定义委托,则每次运行 Windows 窗体应用程序时都必须将上百个委托类型加载到内存中。Windows 窗体框架可依赖很少的委托类型在 Form 类和各种控件类中定义上百个事件,从而提供更好的内存利用率。

最小化委托类型数量的第二个目的是,利用可插接式处理程序方法来增加实现多态性的可能。当您使用与 EventHandler 委托匹配的调用签名来编写处理程序方法时,可以将其绑定到大多数由窗体及其控件引发的事件上。

让我们来看一些编写通用事件处理程序的示例。首先介绍这样一个示例:在这个示例中,可以通过将用户输入改为大写来响应窗体中多个文本框的 TextChanged 事件。没必要为每个控件都创建单独的事件处理程序。相反,您可以只创建一个事件处理程序,然后将其绑定到多个不同文本框的 TextChanged 事件上(请参见图 3)。

对于这个示例,首先应该注意的是,Handles 子句并不仅限于一个事件。您可以在 Handles 关键字后面使用由逗号分隔的列表来包括任意数量的事件。在本示例中,使用了 TextChangedHandler 方法来创建三个不同的事件处理程序。因此,当用户更改这三个文本框中任意一个的文本时,都将执行这个方法。

当执行 TextChangedHandler 方法时,如何知道是哪个 TextBox 对象引发该事件呢?这就是 sender 参数要解决的问题。请记住,sender 参数是根据通用类型 Object 传递的。这意味着,在针对其编程之前,必须将它转换成一个更具体的类型。在前面的示例中,要访问 sender 参数的 Text 属性,就必须将该参数转换为 TextBox。

如果您曾经使用 Visual Basic 的早期版本生成了基于窗体的应用程序,则您可能习惯于使用控件数组。在 Visual Basic 6.0 中使用控件数组的主要优势在于,此功能使得创建一个能够响应由多个不同控件引发的事件的处理程序方法成为可能。Visual Basic .NET 不支持控件数组。然而,您无需过度紧张,因为您刚才已经看到,Visual Basic .NET 提供了一种替代技术,可以将一个处理程序方法绑定到多个不同的事件上。

.NET Framework 的事件体系结构还为您提供了控件数组无法实现的功能。例如,您可以创建一个处理程序方法来响应由多个不同类型的控件所引发的事件。图 4 显示了一个处理程序方法示例,它绑定到三个不同控件类型上的三个不同的事件上。

正如您所看到的,将处理程序方法绑定到事件的方案相当灵活。唯一的要求是,处理程序方法和它绑定到的事件应基于相同的委托类型。而 .NET Framework 中有相当多的事件都是基于 EventHandler 委托类型的,这使得编写通用处理程序方法十分简单。

当您编写通用处理程序方法时,有时需要编写代码来执行条件操作,而这些操作只在事件源是某种特定类型的对象时才执行。例如,您的处理程序方法可以使用 TypeOf 运算符来检查 sender 参数。这使得您的处理程序方法可以在事件源为 Button 对