(转)事件和路由事件概述

我们介绍在使用 C#、Microsoft Visual Basic 或 C++ 作为编程语言并使用 XAML 定义 UI 时,事件的编程概念。你可以在 XAML 中的 UI 元素声明中为事件分配处理程序,或者可以使用特定于语言的语法在代码中添加处理程序。Windows 运行时支持路由事件,借助此功能,某些输入事件和数据事件可由引发该事件的对象以外的对象来处理。在定义控件模板或集中化应用页面或布局容器的事件逻辑时,路由事件非常有用。

 

事件即编程概念

一般而言,当使用 C#、Microsoft Visual Basic 或 C++ 作为编程语言并使用 XAML 定义 UI 时,事件在概念上类似于你最喜欢的语言中的事件模型。如果你已知道如何使用 Microsoft .NET 事件,那么你已领先一步。但是你没有必要了解 .NET 事件模型的太多知识即可执行一些基本任务,例如附加处理程序。

当你使用 C#、Visual Basic 或 C++ 作为编程语言时,UI 在标记中定义 (XAML)。对于 XAML 标记语法,将 UI 事件从标记元素连接到运行时代码实体的一些原则与其他 Web 技术类似,例如 ASP.NET 或 HTML5。

为 XAML 定义的 UI 提供运行时逻辑的代码常常称为代码隐藏或代码隐藏文件。在 Microsoft Visual Studio 解决方案视图中,此关系以图形方式显示,代码隐藏文件是一个独立、嵌套的文件,而不是它引用的 XAML 页面。

按钮.单击:事件和 XAML 简介

Windows 应用商店应用的一个最常见的编程任务是捕获用户在 UI 上的输入。例如,你的 UI 可能有一个按钮,用户必须单击它才能提交信息或更改状态。

通过生成 XAML 来定义 Windows 应用商店应用的 UI。此 XAML 可以是一个设计器(例如 Microsoft Expression Blend)或 Visual Studio 中一个设计界面的输出。也可在一个明文编辑器或第三方 XAML 编辑器中编写此 XAML。在生成该 XAML 的过程中,你可以在定义所有其他建立该 UI 元素的 XAML 属性值时,连接各个 UI 元素的事件处理程序。

XAML 中的事件连接包括为在代码隐藏文件中定义的处理程序方法指定字符串形式的名称。例如,此 XAML 定义一个 Button 对象,它的一些重要特性分配为属性,并为按钮的 Click 事件连接一个处理程序:

<Button x:Name="showUpdatesButton"
  Content="{Binding ShowUpdatesText}"
  Click="showUpdatesButton_Click"/>

使用你选择的编程语言编写实际的处理程序,例如 Visual Basic 或 C#。在属性 Click="showUpdatesButton_Click" 中,你创建了一个合约:当对 XAML 进行标记编译和分析时,开发环境的构建操作和最终 XAML 运行时分析操作中的 XAML 标记编译步骤都可以找到一个名为 showUpdatesButton_Click 的方法。而且,showUpdatesButton_Click 必须是一个方法,并且该方法要为 Click 事件的任何处理程序实现一个兼容的方法签名(基于一个委托)。 例如,此代码定义 showUpdatesButton_Click 处理程序。

  1. private void showUpdatesButton_Click (object sender, RoutedEventArgs e) {
  2.     Button b = sender as Button;
  3.     //more logic to do here...
  4. ...
  5. }
Private Sub showUpdatesButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
    Dim b As Button = CType(sender, Button)
    ' more logic to do here...
End Sub
void MyNamespace::BlankPage::showUpdatesButton_Click(Platform::Object^ sender, Windows::UI::Xaml::Input::RoutedEventArgs^ e) {
    Button^ b = (Button^) sender;
    //more logic to do here...
}

提示  Visual Studio 提供了一种便捷方式来命名事件处理程序,并通过 Microsoft IntelliSense 定义处理程序方法。当在 XAML 文本编辑器中提供事件的特性名称时,稍等片刻就会显示一个 IntelliSense 列表。如果单击该列表中的 <新建事件处理程序>,Microsoft Visual Studio 将基于元素的 x:Name(或类型名)、事件名称和数字后缀建议一个方法名称。然后可以右键单击所选的事件处理程序名称,单击“导航到事件处理程序”。这将直接导航到新插入的事件处理程序定义,如代码编辑器中所示。事件处理程序已拥有正确的签名,包括 sender 参数和该事件所使用的特定事件数据类。另外,如果代码隐藏文件中已存在一个具有正确签名的处理程序方法,该方法的名称会与 <新建事件处理程序> 选项一起显示在自动完成下拉列表中。也可以按下 Tab 键来代替单击 IntelliSense 列表项。

定义事件处理程序

对于充当 UI 元素并在 XAML 中声明的对象,事件处理程序代码必须在一个分部类中定义,该类用作 XAML 页面的代码隐藏。事件处理程序是你编写的方法,是与 XAML 关联的分部类中的一部分。这些事件处理程序基于一个特定事件使用的委托。事件处理程序方法可以是公共的或私有的。私有访问可以使用,原因在于 XAML 创建的处理程序和实例会在最终生成代码时合并在一起。一般而言,建议让事件处理程序方法在类中保持私有。

注意  针对 C++ 的事件处理程序不会在分部类中定义,它们在标头中声明为私有类成员。

发送方参数和事件数据

为事件编写的处理程序可访问两个值,它们分别用作在调用该处理程序的每种情况下的输入。第一个值是 sender,它是处理程序所附加到的对象的引用。sender 参数的类型设置为基础 Object 类型。一种常见技术是将 sender 转换为一种更准确的类型。如果期望检查或更改 sender 对象本身上的状态,此技术很有用。基于你自己的应用设计,你想要一种可将 sender 安全地转换到的类型(基于处理程序附加在何处或其他设计细节)。

第二个值是事件数据,它通常在签名中显示为 e 参数。你可以通过查看委派(分配给你正在处理的特定事件)的 e 参数,然后使用 Visual Studio 中的 IntelliSense 或对象浏览器,发现事件数据的哪些属性可用。或者可以使用 Windows 运行时参考文档。

对于一些事件,事件数据的具体属性值与知道已引发该事件同样重要。这对于输入事件尤其如此。对于指针事件,在事件发生时指针的位置可能很重要。对于键盘事件,所有可能的按键都会产生 KeyDownKeyUp 事件。要确定用户按下了哪个键,必须访问可供事件处理程序使用的 KeyRoutedEventArgs。 输入事件和输入场景常常涉及到本文未介绍的其他考虑因素,例如针对指针事件的指针捕获,以及针对键盘事件的修改键和平台键代码。

 

在代码中添加事件处理程序

XAML 不是向对象分配事件处理程序的唯一方式。要在代码中向任何给定对象添加事件处理程序,包括无法在 XAML 中使用的对象,可以使用特定于语言的语法来添加事件处理程序。

在 C# 中,语法是使用 += 运算符。你可在运算符右侧引用事件处理程序方法名称来注册处理程序。

如果使用代码向运行时 UI 中显示的对象添加事件处理程序,一种常见的做法是添加这些处理程序来响应对象生存期事件或回调,例如 LoadedOnApplyTemplate,这可使相关对象上的事件处理程序在运行时准备好处理用户发起的事件。此示例首先给出了 XAML 大纲,提供了一些结构信息,然后提供了 C# 语言语法。

<Grid x:Name="LayoutRoot" Loaded="LayoutRoot_Loaded">
  <StackPanel>
    <TextBlock Name="textBlock1">Put the pointer over this text</TextBlock>
...
  </StackPanel>
</Grid>
  1. void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
  2. {
  3.     textBlock1.PointerEntered += textBlock1_PointerEntered;
  4.     textBlock1.PointerExited += textBlock1_PointerExited;
  5. }

还有一种详细语法(verbose syntax)。 在 2005 年,C# 添加了一个称为委托推断(delegate inference)的概念,它使编译器能够推断新委托实例并实现以前更简单的语法。详细语法在功能上等同于以前的示例,但显式创建了一个新委托实例,然后再注册它,进而避免利用委托推断。这种显式的语法不太常见,但你仍会在一些代码示例中看到它。

void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
{
    textBlock1.PointerEntered += new PointerEventHandler(textBlock1_PointerEntered);
    textBlock1.PointerExited += new MouseEventHandler(textBlock1_PointerExited);
}

Visual Basic 语法有两种可能性。一种类似于 C# 语法,将处理程序直接附加到实例。这需要 AddHandler 关键字和取消引用处理程序方法名称的 AddressOf 运算符。

Private Sub LayoutRoot_Loaded(ByVal sender As Object, ByVal e As PointerRoutedEventArgs e)
    AddHandler textBlock1.PointerEntered, AddressOf textBlock1_PointerEntered
    AddHandler textBlock1.PointerExited, AddressOf textBlock1_PointerExited
End Sub

Visual Basic 语法的另一种选择是在事件处理程序上使用 Handles 关键字。此技术适用于处理程序应在加载时存在于对象上并在整个对象生存期持久化的情况。在 XAML 中定义的一个对象上使用 Handles,需要你提供一个 Name / x:Name。此名称成为 Handles 语法的 Instance.Event 部分所需的实例限定符。在此情况下,你无需一个基于对象生存期的事件处理程序,即可开始附加其他事件处理程序;在编译 XAML 页面时会创建 Handles 连接。

Private Sub textBlock1_PointerEntered(ByVal sender As Object, ByVal e As PointerRoutedEventArgs) Handles textBlock1.PointerEntered
'...
End Sub

注意  Visual Studio 和它的 XAML 设计界面一般都提倡使用实例处理技术代替,而不是 Handles 关键字。这是因为在 XAML 中建立事件处理程序连接是典型的设计人员-开发人员工作流中的一部分,并且 Handles 关键字技术与在 XAML 中连接事件处理程序不兼容。

在 C++ 中,你还可使用 += 语法,但请注意与基本 C# 形式有区别:

  • 不存在委托推断,所以必须为委托实例使用 ref new 关键字。
  • 委托构造函数有两个参数,并且需要目标对象作为第一个参数。通常由你指定 this
  • 委托构造函数需要方法地址作为第二个参数,所以 & 在方法名称之前取消引用。
textBlock1->PointerEntered += 
ref new PointerEventHandler(this,&BlankPage::textBlock1_PointerExited);

路由事件

对于某些存在于大部分 UI 元素上的事件,使用 C#、Visual Basic 或 C++ 的 Windows 运行时支持路由事件的概念。这些事件支持输入和用户交互,在 UIElement 基类上实现。以下是一组属于路由事件的输入事件:

路由事件是一种可能从一个子对象(按一定路线)传递到对象树中它的每个连续的父对象的事件。UI 的 XAML 结构大体类似于此树,该树的根是 XAML 中的根元素。真正的对象树可能与 XAML 稍有区别,因为对象树不包含 XAML 语言功能,例如属性元素标记。一般而言,你可以将路由事件视为从 XAML 对象元素中引发事件的子元素向包含它们的父对象元素浮升。事件及其事件数据可在事件路由上的多个对象上处理,该路由可能一直延伸到根元素。

如果了解 Web 技术,例如动态 HTML (DHTML) 或 HTML5,你可能已熟悉浮升概念。

当一个路由事件沿它的事件路由浮升时,任何附加的事件处理程序都会访问事件数据的一个共享实例。因此,如果任何事件数据对处理程序而言是可写的,对事件数据的任何更改将传递到下一个处理程序,而且可能不再表示来自于该事件的原始事件数据。当一个事件具有路由事件的行为时,参考文档将包含有关路由行为的备注或其他表示法。

RoutedEventArgs 的 OriginalSource 属性

当一个事件朝一个事件路线浮升时,sender 不再是与引发事件的对象相同的事件。相反,sender 是所调用的处理程序附加到的对象。

在某些情况下,sender 不是感兴趣的对象,你感兴趣的是一些信息,例如指针在哪个可能的子对象上方,或者在用户按下键盘上的键时哪个对象拥有焦点。对于这些情形,我们使用 OriginalSource 属性的值。 在路线上的所有点上,OriginalSource 都会向引发事件的原始对象报告,而不是向所附加的处理程序报告。但是,对于 UIElement 输入事件,该原始对象常常是一个不会在页面级 XAML 中直接看到的对象。相反,该对象可能是控件的一个模板部分。例如,如果用户将指针悬停在一个 Button 的边缘,对于大部分指针事件,OriginalSourceTemplate 中的模板部分,而不是 Button 本身。在默认模板中,该部分是基础 XAML 模板中的一个 Border,并且该 Border 作为 OriginalSource 的值传递。

提示  如果创建模板化控件,输入事件浮升对你特别有用。对于任何具有模板的控件,它的使用者都可能应用一个新模板。如果使用者声明一个新模板,使用者可能会意外地消除事件模板 XAML 中声明的任何事件处理。你仍然可以通过在类定义的 OnApplyTemplate 中附加处理程序来提供控件级事件处理,并捕获在实例化时浮升到控件根元素的输入事件,模板行为无法在实例化时改写它。

Handled 属性

特定路由事件的多个事件数据类包含一个名为 Handled 的属性。例如,请参阅 PointerRoutedEventArgs.HandledKeyRoutedEventArgs.HandledDragEventArgs.HandledHandled 是一个可设置的布尔属性。

Handled 属性设置为 true 会影响事件系统的行为。当 Handledtrue 时,大部分事件处理程序的路由都会停止;该事件不会沿该路线继续传递,因此也无法通知其他附加的处理程序所发生的特定事件情况。在事件上下文中将 "handled" 作为一个操作有何含义以及你的应用程序如何响应正在处理的事件,这完全取决于你。但是,如果在事件处理程序中更改 Handled 属性的值,你应该留意事件系统的响应方式。

并非所有路由事件都可以这种方式取消路线。例如, GotFocusLostFocus 会一直路由到根,它们的事件数据类没有可影响该行为的 Handled 属性。

控件中的输入事件处理程序

一些特定的 Windows 运行时控件有时会在内部为输入事件使用 Handled 概念。这可能使它看起来像一个从不会发生的输入事件,因为用户代码无法处理它。例如, Button 类包含专门处理一般输入事件 PointerPressed 的逻辑。它这么做是因为,按钮引发了一个 Click 事件,该事件最初由指针点击输入引发,以及由其他输入模式引发,例如在拥有焦点时处理某些键。出于类设计的用途,原始输入事件会从概念上进行处理,类使用者(例如你的用户代码)能够选择处理还是不处理更能代表控件的 Click 事件。Windows 运行时 API 参考中针对特定控制类的主题常常会提到该类实现的事件处理行为。在某些情况下,可通过改写 OnEvent 方法来更改此行为。例如,可通过重写 Control.OnKeyDown,更改 TextBox 派生类响应键输入的方式。

注册已处理的路由事件的处理程序

前面我们已经提到,将 Handled 设置为 true 会阻止调用大部分处理程序。但是, AddHandler 方法提供了一种技术,可通过该技术附加一个始终为该路由调用的处理程序,即使该路由中其他某个以前的处理程序已在共享事件数据中将 Handled 设置为 true。如果你使用的一个控件已在其内部组合过程中或针对特定于控件的逻辑处理过该事件,但你仍然希望在一个控件实例或路由中更高的级别响应它,这种技术将很有用。但是,此技术应谨慎使用,因为它可能与 Handled 的用途相矛盾,并且可能违背控件的目标用法或对象模型。

只有具有相应路由事件标识符的路由事件可使用 AddHandler 事件处理技术,因为该标识符是 AddHandler 方法的必需输入。请参阅 AddHandler 的参考文档,了解可获得路由事件标识符的事件列表。

可视化树外部的路由事件

某些对象与主要可视化树具有一种关系,这种关系在概念上就像在主要可视元素上有一个覆盖图。这些对象没有将所有树元素连接到可视根的常见父-子关系。任何已显示的 PopupToolTip 都属于这种情况。如果你希望从一个 PopupToolTip 处理路由事件,可在 PopupToolTip 内的特定 UI 元素上放置处理程序,而不是在 PopupToolTip 元素本身上。不要依赖于为 PopupToolTip 内容执行的任何组合元素内的路由。这是因为路由事件的事件路由仅适用于主要可视化树。PopupToolTip 不应被视为子 UI 元素的父元素,并且绝不会接收路由事件,即使它尝试使用类似 Popup 默认背景的机制来捕获输入事件区域。

点击测试和输入事件

确定某个元素是否对鼠标、触摸和触笔输入可见的操作称为点击测试。对于触摸操作以及特定于交互的事件或一个触摸操作引起的操作事件,一个元素必须对点击测试可见,以用作事件源并触发与该操作关联的事件。 否则,操作会通过该元素传递到可视化树中的任意基础元素或父元素。 影响点击测试的因素有很多,但你可以通过检查给定元素的 IsHitTestVisible 属性来确定该元素是否会引发输入事件。只有当该元素符合以下条件时,该属性才返回 true

  • 元素的 Visibility 属性值为 Visible
  • 元素的 BackgroundFill 属性值不是 null。一个 null Brush 值会导致透明性和点击测试不可见。(要使一个元素透明但可执行点击测试,可使用 Transparent画笔代替 null。)

    注意  BackgroundFill 不由 UIElement 定义,而是由不同的派生类定义,例如 ControlShape。但你为前景和背景属性使用的画笔含义对点击测试和输入事件而言是相同的,无论是哪些子类实现了这些属性。

  • 如果该元素为控件,那么它的 IsEnabled 属性值必须为 true
  • 该元素必须具有实际的大小。 ActualHeightActualWidth 为 0 的元素不会产生输入事件。

某些控件对点击测试有特殊规则。例如, TextBlock 没有 Background 属性,但它仍然可在其大小的整个区域内进行点击测试。 ImageMediaElement 控件可在它们定义的矩形大小上执行点击测试,无论显示的媒体源文件中显示了何种透明内容,例如 alpha 通道。

大部分 Panel 类和 Border 都不能在它们自己的后台进行点击测试,但仍然可以处理从它们包含的元素中路由的用户输入事件。

你可以确定哪些元素与用户输入事件的位置相同,而不论这些元素是否可进行点击测试。为此,请调用 FindElementsInHostCoordinates 方法。顾名思义,该方法在相对于指定主机元素的位置查找元素。但是,你应该注意应用的转换和布局更改会影响元素的坐标系统,因此也会影响给定位置上找到的元素。

命令处理

少量 UI 元素支持命令处理。命令处理在其基础实现中使用了与输入相关的路由事件,支持通过调用单个命令处理程序来处理相关的 UI 输入(某种指针操作,一种特定的加速键)。如果命令处理可用于 UI 元素,可以考虑使用它的命令处理 API 代替任何具体的输入事件。有关详细信息,请参阅 ButtonBase.Command

本文引用:

http://technet.microsoft.com/zh-cn/ie/hh758286

posted on 2012-11-26 17:03  憨熊之家  阅读(1566)  评论(0编辑  收藏  举报

导航