路由事件和命令

MSDN: http://msdn.microsoft.com/zh-cn/magazine/cc785480.aspx

路由事件概述


刚开始接触 WPF 时,您可能会在自己并不知晓的情况下就用到了路由事件。例如,当您在 Visual Studio ® 设计器中向窗口添加一个按钮,并将其命名为 myButton,然后双击该按钮时,Click 事件将挂接在您的 XAML 标记之内,它的事件处理程序会添加到 Window 类的代码隐藏中。这种感觉与在 Windows 窗体和 ASP.NET 中挂接事件并无二致。实际上,它比较接近 ASP.NET 的代码编写模型,但更类似 Windows 窗体的运行时模型。具体来说,在按钮的 XAML 标记中,代码的结尾类似如下所示:

<Button Name="myButton" Click="myButton_Click">Click Me</Button>
挂接事件的 XAML 声明就象 XAML 中的属性分配,但结果是针对指定事件处理程序的对象产生一个正常的事件挂接。此挂接实际上出现在编译时生成的窗口局部类中。要查看这一挂接,转到类的构造函数,右键单击 InitializeComponent 方法调用,然后从上下文菜单中选择“转到定义”。编辑器将显示生成的代码文件(其命名约定为 .i.g.cs 或 .i.g.vb),其中包括在编译时正常生成的代码。在显示的局部类中向下滚动到 Connect 方法,您会看到下面的内容:

#line 6 "..\..\Window1.xaml"
this.myButton.Click += 
  new System.Windows.RoutedEventHandler(
  this.myButton_Click);
这一局部类是在 编译时从 XAML 中生成的,其中包含那些需要设计时编译的 XAML 元素。大部分 XAML 最终都会成为编译后程序集中嵌入了二进制的资源,在运行时会与二进制标记表示的已编译代码合并。
如果看一下窗口的代码隐藏,您会发现 Click 处理程序如下所示:

private void myButton_Click(
  object sender, RoutedEventArgs e) { }
到目前为止,它看起来就象任何其他 .NET 事件挂接一样——您有一个显式声明的委托,它挂接到一个对象事件且委托指向某个处理方法。使用路由事件的唯一标记是 Click 事件的事件参数类型,即 RoutedEventArgs。那么路由事件究竟有何独特之处呢?要理解这一点,首先需要了解 WPF 元素化的组合模型。

WPF 元素树
如果您在项目中开启一个新窗口并在设计器中将按钮拖入窗口内,您会得到 XAML 格式的元素树,如下所示(为了清楚略去了属性):
复制代码
<Window>
  <Grid>
    <Button/>
  </Grid>
</Window>
其中的每个元素都代表对应 .NET 类型的一个运行时实例,元素的声明分层结构形成了所谓的逻辑树。此外,WPF 中的许多控件不是 ContentControl 就是 ItemsControl,这代表他们可以有子元素。例如,Button 是一个 ContentControl,它可以将复杂的子元素做为其内容。您可以展开逻辑树,如下所示:
复制代码
<Window>
  <Grid>
    <Button>
      <StackPanel>
        <Image/>
        <TextBlock/>
      </StackPanel>
    </Button>
  </Grid>
</Window>
生成的 UI 如 图 1 所示。
图 1 包含按钮内容的简单窗口
如您所想,树可以有多个分支(Grid 中的另一 Button),因此逻辑树会变得极为复杂。对于逻辑树的 WPF 元素,您需要意识到您所见到的并不是您在运行时真正得到的内容。每个这样的元素通常都会在运行时扩展为更为复杂的可视元素树。在本例中,元素的逻辑树扩展为可视元素树,如 图 2 所示。
图 2 简单窗口可视树
我使用名为 Snoop 的工具 ( blois.us/Snoop) 查看 图 2 中所示可视树的元素。您可以看到窗口 (EventsWindow) 实际是将其内容置入 Border 和 AdornerDecorator 之内,用 ContentPresenter 显示其中的内容。按钮也与此类似,将其内容置入 ButtonChrome 对象,然后用 ContentPresenter 显示内容。
单击按钮时,我可能实际根本没有单击 Button 元素,可能是单击可视树中的某一子元素,甚至是逻辑树中未显示的元素(如 ButtonChrome)。例如,假设我在按钮内的图像上方单击鼠标。这一单击操作在一开始实际是将其表达为 Image 元素中的 MouseLeftButtonDown 事件。但却需要转化为 Button 层级的 Click 事件。这就要引入路由事件中的路由。

事件路由

对逻辑树和可视树有所了解很有必要,因为路由事件主要是根据可视树进行路由。路由事件支持三种路由策略: 气泡、隧道和直接。
气泡事件最为常见,它表示事件 从源元素扩散(传播)到可视树,直到它被处理或到达根元素。这样您就可以针对源元素的上方层级对象处理事件。例如,您可向嵌入的 Grid 元素附加一个 Button.Click 处理程序,而不是直接将其附加到按钮本身。气泡事件有指示其操作的名称(例如,MouseDown)。

隧道事件采用另一种方式, 从根元素开始,向下遍历元素树,直到被处理或到达事件的源元素。这样上游元素就可以在事件到达源元素之前先行截取并进行处理。根据命名惯例,隧道事件带有前缀 Preview(例如 PreviewMouseDown)。

直接事件类似 .NET Framework 中的正常事件。该事件唯一可能的处理程序是与其挂接的委托。
通常,如果为特殊事件 定义了隧道事件,就会有相应的气泡事件。在这种情况下, 隧道事件先触发,从根元素开始,下行至源元素,查找处理程序。一旦它被处理或到达源元素,即会触发气泡事件,从源元素上行,查找处理程序。气泡或隧道事件不会仅因调用事件处理程序而停止路由。如果您想中止隧道或气泡进程,可使用您传递的事件参数在事件处理程序中将事件标记为已处理。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值