介绍一下事件的编程概念、事件概念如何在 Silverlight 中发挥作用及其编程模型。
Silverlight 事件与公共语言运行时和 .NET Framework 对于事件概念的定义在本质上是相同的。与 WPF 类似,我们可以将事件的处理程序分配为 XAML 中 UI 元素的声明的一部分,也可以使用语言特定的语法在代码中添加处理程序。Silverlight 支持路由事件的概念,通过路由事件这一功能,某些输入事件和数据事件可以由除引发事件的对象之外的其他对象进行处理。当组合控件模板或集中处理应用程序页的事件逻辑时,路由事件特别有用。
一.事件作为编程概念
事件是对象发送的消息,以发信号通知操作的发生。操作可能是由用户交互(例如鼠标单击)引起的,也可能是由某个类的内部逻辑触发的。引发事件的对象称为事件发送方。捕获事件并对其作出响应的对象叫做事件接收方。总体而言,事件的作用是交流某个对象在运行时的时间特定的、相对轻量的信息,并可能将该信息传送到应用程序中的其他对象。
Silverlight 事件
一般而言,Silverlight 事件是 CLR 事件,因此是可以使用托管代码来处理的事件。
由于 UI 是在标记 (XAML) 中定义的,因此,其编程的某些原则类似于 ASP.NET或使用 HTML DOM。
Silverlight 还有一些事件要求插件实例本身具有处理程序,例如 OnError 事件。这些事件由针对 HTML DOM 中的插件实例运行的任何脚本公开,并可能由此脚本进行处理。HTML 脚本应具有第一个机会进行处理,因此,这些事件不传递到托管 API。我们的托管宿主网页中就有这样的代码。
说明:因为 Silverlight 在宿主浏览器的插件体系结构中工作,所以,输入事件的输入促进因素最初由浏览器处理。然后,输入信息通过该浏览器用于支持一般插件的 API 发送到 Silverlight 插件,您可以处理在 Silverlight API 中定义的相应事件。Silverlight 中大多数其他的非输入事件并不一定具有此类浏览器问题。
二.Button.Click:使用 Silverlight 事件简介
由于 Silverlight 属于 UI 技术,因此,我们要执行的最常见任务之一是捕获用户对 UI 的输入。例如,UI 可能具有一个按钮,用户必须单击此按钮才能提交信息或更改应用程序的状态。
通常,我们可以通过生成 XAML 为基于 Silverlight 的应用程序定义 UI。此 XAML 可以是来自诸如 Expression Blend的设计器和诸如 Silverlight Designer for Visual Studio 的设计图面的输出,也可能是在文本编辑器中编写出来的。作为生成该 XAML 的一部分,我们可以在定义用于描述该 UI 元素的所有其他属性的同时,为各个 UI 元素连结事件处理程序。XAML 中的事件连结涉及到提供处理程序方法的名称。
例如,下面的XAML代码定义一个Button对象,并为按钮的Click事件连接一个处理程序:
<Button Name="UpdateButton" Content="Update" Click="showUpdatesButton_Click"/>
而实际的处理程序是用Silverlight支持的.net框架语言编写的。如(vb,c#)
通过Click=”showUpdatesButton_Click”,我们已经创建一个协定,当编译XAML时,XAML编译器可以找到一个名为showUpdatesButton_Click的方法。此外对于这个方法还有这样的一些规定:它实现特定的方法签名(基于委托),而该方法签名是Click事件的任何处理程序所必须的。
定义 showUpdatesButton_Click 处理程序:
private void showUpdatesButton_Click (object sender, RoutedEventArgs e)
{
//业务逻辑
}
我们编写的处理程序必须与该委托签名匹配:上面的示例,委托为RouteEventHandler
要强调的一点:调用 Silverlight 事件处理程序函数时不能使用参数值(即使是空参数值也不例外),这一点与 HTML DOM 中的事件处理程序语法明显不同。属性值只是引用处理程序名称,而其他机制(例如,处理程序的预期输入参数)负责传递信息。
三:定义事件处理程序
对于作为在 XAML 中声明的 UI 的对象,必须在充当 XAML 页的代码隐藏的分部类中定义事件处理程序代码。
分部类中的事件处理程序根据由该特定事件使用的 CLR 委托编写为方法。事件处理程序方法可以是公共方法,也可以具有私有访问级别。私有访问将发挥作用,因为由 XAML 创建的处理程序和实例最终将通过代码生成来进行联接。一般建议是不要让事件处理程序方法在类中成为公共方法。
四.sender参数和事件参数
我们为托管的 Silverlight 事件编写的任何处理程序都可以访问两个值,这两个值对于调用处理程序的每种情况都可以用作输入。第一个此类值是 sender。sender 是对于在其中附加处理程序的对象的引用。sender 类型化为基本 Object 类型。如果您预计要检查或更改 sender 对象自身的状态,并且您知道根据处理程序所附加到的位置或有关应用程序的其他特定信息,可以安全地将 sender 强制转换为某种类型,则一种常用的 Silverlight(和 WPF)事件处理方法是将 sender 强制转换为更精确的类型。
对于某些事件,EventArgs 派生类中的事件数据与知道事件被引发同样重要。对于输入事件尤其如此。对于鼠标事件,事件发生时鼠标的位置可能很重要。对于键盘事件,键盘上的键引发相同的 KeyUp 和 KeyDown 事件。为了确定按下了哪个键,必须访问可用于事件处理程序的 KeyEventArgs。
五.在托管代码中添加事件处理程序
XAML 并不是向对象分配事件处理程序的唯一方式。若要在托管代码中将事件处理程序添加到任何给定对象(包括添加到甚至在 XAML 中不可用的对象),可以使用特定于 CLR 语言的语法来添加事件处理程序。在 C# 中,此语法是使用 += 运算符,并通过使用事件处理程序方法名称来声明新委托以实例化处理程序。
如果您使用代码将事件处理程序添加到的确在运行时 UI 中显示的对象,则 Silverlight 的通用做法是添加此类处理程序以响应对象生存期事件(例如,Loaded 或 OnApplyTemplate),以便相关对象上的事件处理程序准备好处理在运行时用户启动的事件。下面的示例首先介绍了 XAML 概况以提供有关结构的一些思路,然后给出了 C# 语言语法。
<Grid x:Name="LayoutRoot" Loaded="LayoutRoot_Loaded"> <StackPanel> <TextBlock Name="textBlock1">Put the mouse over this text</TextBlock> </StackPanel> </Grid> | |
.xaml.cs代码 | |
void LayoutRoot_Loaded(object sender, RoutedEventArgs e) { textBlock1.MouseEnter += new MouseEventHandler(textBlocks_MouseEnter); textBlock1.MouseLeave += new MouseEventHandler(textBlocks_MouseLeave); } |
六.路由事件
对于在基类中定义的并在支持用户交互和输入的大多数 UI 元素中存在的若干输入事件,Silverlight 支持路由事件的概念:
· KeyDown
· KeyUp
· GotFocus
· LostFocus
· MouseLeftButtonDown
· MouseLeftButtonUp
· MouseMove
· MouseWheel
· BindingValidationError
路由事件是在对象树中可能从某个子对象传递(路由)到其每个后续父对象的事件。所涉及的对象树与 UI 的 XAML 结构近似,该树的根成为 XAML 中的根元素。真正的对象树可能与 XAML 稍有不同,因为对象树不包括 XAML 语言功能(如属性元素标记),但通常可将路由事件视为从引发该事件的 XAML 对象元素子项向包含它们的父对象元素"冒泡"并持续路由,直到到达根元素。
说明一下:
WPF 支持类似的"隧道"路由策略,因此,页/对象树的根首先有机会来处理路由事件,然后,该事件通过"隧道"沿着对象树向下传递到其事件源。Silverlight 不使用"隧道"传递路由事件。Silverlight 中的事件要么遵循"冒泡"路由策略(也称为路由事件),要么根本不路由。Silverlight 与 WPF 之间的路由事件行为还存在着其他 API 级别差异。
RoutedEventArgs 的 OriginalSource 属性
当事件向上冒泡事件路由时,sender 不再是与事件引发对象相同的对象。相反,sender 是在其中附加正在调用的处理程序的对象。在许多情况下,sender 不是您感兴趣的对象;您而是更希望了解鼠标位于哪些可能的子对象上方,或当按下键盘键时哪个对象具有焦点等此类信息。对于这些情形,OriginalSource 属性的值是您感兴趣的对象。
在路由的所有点上,OriginalSource 报告引发事件的原始对象,而不是附加处理程序的位置。对于上述方式有用的示例方案,请考虑这样一个应用程序:在该应用程序中,您希望某些键组合成为"热键"或快捷键,而不考虑哪个控件当前持有键盘焦点并启动事件。就对象树而言,具有焦点的对象可能嵌套在列表框中包括某些项的列表内,也可能是整个用户界面中的众多对象之一。如果必须将处理程序附加到应用程序中每个可能获得焦点的对象才能检测到快捷键,则这很明显是不切实际的。但是,因为键盘事件正在冒泡路由事件,所以,该事件最终会在其路由中到达根对象。因此,您通常可以在根对象上附加单个 KeyDown 处理程序,并依赖于 KeyDown 事件最终冒泡到根对象的行为。然后,处理程序可以确定该键是否为预期快捷键操作的有效组合,或者是否不需要执行操作。
Handled 属性
特定路由事件的多个事件数据类包含一个名为 Handled 的属性。有关示例,请参见 MouseButtonEventArgs..::..Handled、KeyEventArgs..::..Handled、MouseWheelEventArgs..::..Handled 和 ValidationErrorEventArgs..::..Handled。Handled 是一个可设置的布尔值属性。
将 Handled 属性设置为 true 会影响 Silverlight 中的事件系统。当您在事件数据中将该值设置为 true 时,路由将因为大多数事件处理程序而停止;事件不会继续路由来向其他附加的处理程序通知该特定的事件。"已处理"作为一个操作在事件上下文中意味着什么以及应用程序如何响应完全取决于您。但是,当您选择在事件处理程序中设置 Handled 时,应确保注意 Silverlight 事件系统的这一行为。
并非所有路由事件都可用这种方法取消。GotFocus、LostFocus 和 MouseMove 将始终一直路由到根目录,其事件数据类没有可影响该行为的 Handled 属性。因此,针对这些事件检查 OriginalSource 值通常是一个好主意,可确保您不处理事件,也不对事件的含义和如何确定其来源进行不正确的假设。
可视化树外的路由事件
Silverlight 中的某些对象涉及与主要可视化树的关系,主要可视化树在概念上类似在主要可视化元素上重叠,并且不是将所有树元素连接到可视根的常见父-子关系的一部分。任何显示的 Popup 或 ToolTip 就是这种情况。如果您要处理来自 Popup 或 ToolTip 的路由事件,则应将处理程序放置于 Popup 或 ToolTip 内的特定 UI 元素上(而非 Popup 或 ToolTip 元素本身上),并且不应依赖于为 Popup 或 ToolTip 内容实现的任何合成内的路由。这是因为对路由事件的事件路由仅适用于主可视化树。Popup 或 ToolTip 不视为子一级 UI 元素的父级,并且永远不接收路由事件,即使正在尝试使用类似 Popup 默认背景这样的内容作为输入事件的捕获区域。
七.用户启动的事件、全屏模式
Silverlight 坚持只允许某些操作在处理用户启动的事件的处理程序的上下文中执行:
· 设置 IsFullScreen
· 显示 SaveFileDialog
· 从 HyperlinkButton 导航
Silverlight 用户启动的事件包括鼠标事件(如 MouseLeftButtonDown)以及键盘事件(如 KeyDown)。基于此类事件(如 Click)的控件的事件也被视为用户启动的事件。
一旦 Silverlight 处于全屏模式,将出于安全原因而特意禁止某些输入事件。
八.移除事件处理程序
在某些情况下,我们可能希望在应用程序生存期中移除事件处理程序。若要移除事件处理程序,请使用特定于 CLR 语言的语法。在 C# 中,使用 -= 运算符。
{ ... textBlock1.MouseEnter -= textBlocks_MouseEnter; } |
如果事件是通过 XAML 属性添加的,则也可以移除处理程序。如果我们为在其中附加处理程序的元素提供了 Name 值,则这一点实现起来更为容易,因为该元素稍后为代码提供了对象引用,但我们还可以在对象树中导航以查找所需的对象引用(如果我们必须这么做)。