Framework 类库的事件编程
http://msdn.microsoft.com/zh-cn/library/aa663632.aspx
本页内容
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 对象时执行一组操作,而在事件源为 CheckBox 对象时执行另一组操作,如下所示:
Sub GenericHandler1(sender As Object, e As EventArgs)
If (TypeOf sender Is Button) Then
Dim btn As Button = CType(sender, Button)
'*** program against btn
ElseIf (TypeOf sender Is CheckBox) Then
Dim chk As CheckBox = CType(sender, CheckBox)
'*** program against chk
End If
End Sub
自定义的事件参数
基于 EventHandler 委托的事件通知通常不在 e 参数中发送任何有意义的信息。e 参数通常是无用的,因为它包含 EventArgs.Empty 值或 Nothing 值。然而,.NET Framework 的设计者创建了一个将参数化信息从事件源传递到其事件处理程序的约定。此约定包括自定义事件参数类和自定义委托类型的创建。
由 Form 类引发的鼠标事件为应该如何使用此约定提供了一个很好的示例。有关鼠标位置和按下哪个鼠标键的参数化信息在一个名为 MouseEventArgs的类中建模。MouseEventArgs 类包含了用于跟踪鼠标位置的 X 和 Y 属性,以及用于指示按下哪个鼠标键的 Button 属性。请注意,按照约定,MouseEventArgs 类必须从通用类 EventArgs 继承。
在事件通知中传递参数化信息的约定需要一个自定义委托来补充自定义事件参数类。因此,有一个名为 MouseEventHandler 的委托用于补充 MouseEventArgs 类。该处理程序委托的定义如下:
Delegate Sub MouseEventHandler(sender As Object, e As MouseEventArgs)
现在,假设您希望对一个与鼠标有关的事件(如 Form 类的 MouseDown 事件)作出响应。您可以编写如图 5 所示的处理程序方法。
请注意,e 参数在该处理程序方法的实现中非常有用。e 参数用于确定鼠标位置以及按下哪个鼠标键。所有这些参数化信息都可以通过设计 MouseEventArgs 类来实现。
您可以找到在 Windows 窗体框架中使用的这种参数化约定的其他示例。例如,有一个名为 KeyPressEventArgs 的类,它由一个名为 KeyPressEventHandler 的委托类型补充。此外,ItemChangedArgs 类由一个名为 ItemChangedHandler 的委托类型补充。您可能会遇到其参数化信息也遵循这个约定的其他事件。
参数化自定义事件
作为练习,我们来设计一个自定义事件,以遵循此约定进行参数化。我将使用一个类似于我在最近几期专栏中使用的示例,它包括一个 BankAccount 类。请考虑以下代码片段:
Class BankAccount
Sub Withdraw(ByVal Amount As Decimal)
'*** send notifications if required
If (Amount > 5000) Then
'*** raise event
End If
'*** perform withdrawal
End Sub
End Class
假设要求 BankAccount 对象在每次遇到提款金额大于 $5,000 的情况时都引发一个事件。在引发该事件时,要求您将提款金额作为参数传递给所有已注册的事件处理程序。首先,您应该创建一个新的事件参数类,它从 EventArgs 类继承:
Public Class LargeWithdrawArgs : Inherits EventArgs
Public Amount As Decimal
Sub New(ByVal Amount As Decimal)
Me.Amount = Amount
End Sub
End Class
自定义事件参数类应该设计为:对于事件源需要传递给其事件处理程序的每个参数化值,它都包含一个公共字段。在本例中,LargeWithdrawArgs 类被设计为包含一个名为 Amount 的 Decimal 字段。接下来,您必须创建一个新的委托类型以补充新的事件参数类:
Delegate Sub LargeWithdrawHandler(ByVal sender As Object, _
ByVal e As LargeWithdrawArgs)
按照约定,此委托类型被定义为包含一个名为 sender 的 Object 参数作为第一个参数。第二个参数 e 则基于自定义事件参数类。
现在,您已经创建了自定义事件参数类和补充的委托类型,可以将它们投入使用了。请考察下面的类定义:
Class BankAccount
Public Event LargeWithdraw As LargeWithdrawHandler
Sub Withdraw(ByVal Amount As Decimal)
'*** send notifications if required
If (Amount > 5000) Then
Dim args As New LargeWithdrawArgs(Amount)
RaiseEvent LargeWithdraw(Me, args)
End If
'*** perform withdrawal
End Sub
End Class
它对 LargeWithdraw 事件进行了修改,可以使用 .NET Framework 中的标准约定在事件通知中传递参数化信息。当在 Withdraw 方法中引发 LargeWithdraw 事件时,有必要创建一个新的 LargeWithdrawArgs 类实例,并将其作为参数传递。由于 BankAccount 对象引发了该事件,所以可以使用 Me 关键字来传递 sender 参数,如下所示:
Dim args As New LargeWithdrawArgs(Amount)
RaiseEvent LargeWithdraw(Me, args)
既然您已经了解了如何创建事件源,接下来我们将注意力转到如何为这个事件创建处理程序方法。处理程序方法应该能够通过 e 参数检索它需要的参数化信息。在本例中,处理程序方法将使用 e 参数来检索 Amount 字段的值:
Sub Handler1(sender As Object, e As LargeWithdrawArgs)
'*** retrieve parameterized information
Dim Amount As Decimal = e.Amount
End Sub
图 6 显示了完整的应用程序,当提取大笔金额时,BankAccount 对象将发送事件通知。请注意,此应用程序符合在事件中传递参数化信息的标准公共语言运行库约定。
小结
本节总结了使用 Visual Basic .NET 进行事件编程的基础知识系列内容。前两期专栏介绍了引发和处理事件的机制。本月的专栏则着重介绍在 .NET Framework 中定义的通用事件和委托的编程实例。
您通过 Visual Basic .NET 处理的大多数事件很可能是基于 EventHandler 委托的。如您所见,可以将多个事件绑定到一个处理程序方法上。在这种情况下,知道何时以及如何使用 sender 参数非常重要。您还了解了其他一些使用自定义参数类传递参数化信息的事件。总之,您现在应该可以使用事件驱动的框架(比如 Windows 窗体或 ASP.NET)来进行一些开发工作。
Framework 类库的事件编程的更多相关文章
- .NET Framework 类库
.NET Framework 类库 MSDN == V2.0 == .NET Framework 类库是一个由 Microsoft .NET Framework SDK 中包含的类.接口和值类型组成的 ...
- .NET Framework 类库——C#命名空间大全
引用地址:https://msdn.microsoft.com/zh-cn/library/gg145045.aspx C# using引用时,不知道有哪些命名空间,这下转载收集一篇,方面查找使用. ...
- Visual Studio 中的 .NET Framework 类库
Visual Studio 中的 .NET Framework 类库 .NET Framework 类库由命名空间组成.每个命名空间都包含可在程序中使用的类型:类.结构.枚举.委托和接口. 当您在 V ...
- [CLR via C#]1.6 Framework类库~1.9与非托管代码的互操作性
原文:[CLR via C#]1.6 Framework类库~1.9与非托管代码的互操作性 1.6 Framework类库 1. .NET Framework中包含了Framework类库(Frame ...
- jquery css事件编程 尺寸设置
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- jquery css事件编程 位置 操作
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- .net平台 .net Framework 组织结构 .net Framework类库 CLR C# 介绍
一..net平台 .NET现在可以看成微软的一个品牌,微软有两个非常成功的品牌,那就是Windows和Office. .NET会成为微软的另一个品牌.它不仅仅是一组技术,产品,或服务(微软的服务包括M ...
- 三种方式实现观察者模式 及 Spring中的事件编程模型
观察者模式可以说是众多设计模式中,最容易理解的设计模式之一了,观察者模式在Spring中也随处可见,面试的时候,面试官可能会问,嘿,你既然读过Spring源码,那你说说Spring中运用的设计模式吧, ...
- 七、Framework类库
1.Framework类库简介 .Net Framework类库包含Framework类库(Framework Class Library,FCL).FCL是一组DLL程序集的统称,其中含有数千个类型 ...
随机推荐
- PAT1005—— 继续(3n+1)猜想
卡拉兹(Callatz)猜想已经在1001中给出了描述.在这个题目里,情况稍微有些复杂. 当我们验证卡拉兹猜想的时候,为了避免重复计算,可以记录下递推过程中遇到的每一个数.例如对n=3进行验证的时候, ...
- MySQL之不能保存表格问题
有时候我们制作表格不能保存时出现这样的提示: 我们打开工具: 把“阻止保存要求重新创建表的更改”选项的勾给去了:
- Linux中RM快速删除大量文件/文件夹方法
昨天遇到一个问题,在Linux中有一个文件夹里面含有大量的Cache文件(夹),数量级可能在百万级别,使用rm -rf ./* 删除时间慢到不可接受.Google了一下,查到了一种方法,试用了下确实比 ...
- 20141124-JS函数
函数: 函数是由事件驱动或者当它被调用时执行的可重复色代码块. <head> <script> function hanshu() { alert("你好!" ...
- 【ASP.NET】获取网站目录的方法
获取网站物理路径: HttpRuntime.AppDomainAppPath 获取网站虚拟路径: HttpRuntime.AppDomainAppVirtualPath
- springside出现Description Resource Path Location Type
<?xml version="1.0" encoding="utf-8" ?><beans xmlns="http://www.sp ...
- hibernate 一对多映射关系
1. 单项多对一映射 custom(顾客)与order(订单) :一个顾客可以有多个订单,一个订单只能有一个顾客 配置方法:在多的一端配置<many -to one& ...
- html5定位并在百度地图上显示
在开发移动端 web 或者webapp时,使用百度地图 API 的过程中,经常需要通过手机定位获取当前位置并在地图上居中显示出来,这就需要用到html5的地理定位功能. navigator.geolo ...
- [GeekBand] C++ 高级编程技术 (1)
一.类型转换 class Fraction { public: explicit Fraction(int num, int den=1) : m_numerator(num), m_denomina ...
- Linux美化——终端提示符
1. PS1变量简介[1] PS1是Linux终端用户的一个环境变量,用来说明命令行提示符的设置. 可以使用 man bash命令查看bash手册,找到该变量支持的特殊字符,以及这些特殊字符的意义: ...