Framework 类库的事件编程

编程

本页内容
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)来进行一些开发工作。

请将给 Ted 的问题和意见发送到 [email protected]

Ted Pattison 是 DevelopMentor (http://www.develop.com) 的教师兼课程作者。他已经撰写了几本关于 Visual Basic 和 COM 的书籍,他目前正在撰写一本名为 Building Applications and Components with Visual Basic .NET (Addison-Wesley, 2003) 的书籍。

时间: 2016-02-06

Framework 类库的事件编程的相关文章

[CLR via C#]1.6 Framework类库~1.9与非托管代码的互操作性

原文:[CLR via C#]1.6 Framework类库~1.9与非托管代码的互操作性 1.6 Framework类库 1. .NET Framework中包含了Framework类库(Framework Class Library,FCL). 2. FCL是一组DLL程序集的统称,其中含有数千个类型定义,每个类型公开一些功能.   1.7 通用类型系统 1. CLR是完全围绕类型展开的. 2. 类型为应用程序和其他类型公开了功能.通过类型,用一种编程语言写的代码能与另一种语言写的代码沟通.

更新MFC中的视图,跟踪.NET Framework中的事件

本文配套源码 如何更新MFC中的视图? 如何跟踪.NET Framework 中的事件? 我在 MDI 程序中打算通过 CMainFrame 中的定时器事件来更新所有的子窗口. 视图用于显示许多图表.用如下的代码只能更新当前活动窗口: GetActiveWindow()->GetActiveView()->GetDocument() 是否有其它的方法从 CMDIFrame 类中获得所有的子窗口或者所有的文档? Makarand 你的情况并不罕见.许多采集实时数据的程序需要定时更新屏幕.即使你的

事件编程(一)

在微软 .NET 框架中可以定义托管类事件并用委托和 += 操作符处理这些事件.这种机制似乎很有用,那么在本机 C++ 中有没有办法做同样的事情? 确实如此!Visual C++ .NET 具备所谓统一事件模型(Unified Event Model),它可以像托管类一样实现本机事件(用 __event 关键字),但是由于本机事件存在一些不明显的技术问题,而微软的老大不打算解决这些问题,所以他们要我正式奉劝你不要使用它们.那么这是不是就是说 C++ 程序员与事件无缘了呢?当然不是!可以通过别的方

Spring Framework中的AOP编程之入门篇

编程 作为这个介绍Spring框架中的面向方面编程(Aspect-Oriented Programming,AOP)的系列的第一部分,本文介绍了使您可以使用Spring中的面向方面特性进行快速开发的基础知识.使用跟踪和记录方面(面向方面领域的HelloWorld)作为例子,本文展示了如何使用Spring框架所独有的特性来声明切入点和通知以便应用方面.本系列的第二部分将更深入地介绍如何运用Spring中的所有通知类型和切入点来实现更实用的方面和面向方面设计模式. 本文的目的不是要介绍构成模块化J2

F#入门:使用.NET Framework中的函数式编程技术

本文讨论: 安装 F# F# 语言基础 .NET 互操作性 异步 F# 本文使用了以下技术: .NET Framework, F# 目录 为什么要使用 F#? 安装 F# 您好,F# Let 表达式 关键字 For 管道 F# 也能够处理对象 异步 F# 与 F# 合作 作 为 Microsoft .NET Framework 家族的新成员,F# 提供类型安全.性能以及类似脚本语言的工作能力,所有这些都是 .NET 环境的一部分.此函数式语言由 Microsoft 研究院的 Don Syme 发

我要实现的功能,.NET Framework 类库里面如果没有怎么办。

问题描述 比如说:我要实现的功能,.NETFramework类库里面如果没有怎么办. 解决方案 解决方案二:右击引用,自己添加,或者选择nuget解决方案三:调用systemapi如果这个还是不能.那.换电脑吧.肯定是系统问题.解决方案四:不明觉厉,我们做的功能大多都不是frame自身就能实现的,必须是通过组合,算法,流程之类的,所以如果没有,要么上nuget.org找有没有已经实现了的第三方dll,要么上github看有没有对应的开源项目,要么就是上技术论坛,群之类的求助,当然,你也可以自己实

Zend Framework 2.0事件管理器(The EventManager)入门教程_php实例

概述 EventManger是一个为以下使用情况设计的组件: 复制代码 代码如下: 实现简单的主题/观察者模式 实现面向切面的设计 实现事件驱动的架构 基本的架构允许你添加和解除指定事件的侦听器,无论是在一个实例基础还是一个共享的集合:触发事件:终止侦听器的执行. 快速入门 通常,你将会在一个类中创建一个EventManager. 复制代码 代码如下: use Zend\EventManager\EventManagerInterface; use Zend\EventManager\Event

.net framework 类库调试遇到问题

问题描述 大家有没有在调试状态下,进入framework的类库查看过源码?我按照这个BLOG的步骤配置.http://blogs.msdn.com/sburke/archive/2008/01/16/configuring-visual-studio-to-debug-net-framework-source-code.aspx最后能够从SERVER下载pdb文件.但是VS弹出个对话框让我打开一个*.CS的文件.让我很是郁闷.在模块窗口可以看到Symbol已载入.不知道到底哪出了问题.大家帮忙看

事件编程(二)

在本文的第一部分(事件编程一),我回答了一个关于用 C++ 实现本机事件的问题.讨论了一般意义上的事件并示范了如何用接口为你的类定义事件处理器,事件的处理必须在客户机实现.我的实现有一些缺陷,我承诺过最终要解决掉,本文就来完成这件事情. 在开始之前,先简单回顾一下前面写的那个程序,PrimeCalc.如 Figure 1 所示: Figure 1 计算素数 程序中使用了一个计算素数的类 CPrimeCalculator,这个类发起两个事件:Progress 和 Done.当搜索到素数时,该类触发