对逻辑树和可视树有所了解很有必要,因为路由事件主要是根据可视树进行路由。路由事件支持三种路由策略:气泡、隧道和直接。
气 泡事件最为常见,它表示事件从源元素扩散(传播)到可视树,直到它被处理或到达根元素。这样您就可以针对源元素的上方层级对象处理事件。例如,您可向嵌入 的 Grid 元素附加一个 Button.Click 处理程序,而不是直接将其附加到按钮本身。气泡事件有指示其操作的名称(例如,MouseDown)。
隧道事件采用另一种方式,从根元素开始,向下遍历元素树,直到被处理或到达事件的源元素。这样上游元素就可以在事件到达源元素之前先行截取并进行处理。根据命名惯例,隧道事件带有前缀 Preview(例如 PreviewMouseDown)。
直接事件类似 .NET Framework 中的正常事件。该事件唯一可能的处理程序是与其挂接的委托。
通 常,如果为特殊事件定义了隧道事件,就会有相应的气泡事件。在这种情况下,隧道事件先触发,从根元素开始,下行至源元素,查找处理程序。一旦它被处理或到 达源元素,即会触发气泡事件,从源元素上行,查找处理程序。气泡或隧道事件不会仅因调用事件处理程序而停止路由。如果您想中止隧道或气泡进程,可使用您传 递的事件参数在事件处理程序中将事件标记为已处理。
private void OnChildElementMouseDown(object sender,
MouseButtonEventArgs e) {
e.Handled = true;
}
一 旦您的处理程序将事件标记为已处理,该事件便不会传给任何其他处理程序。这一论断只是部分正确。实际上,事件路由仍在继续起作用,您可利用 UIElement.AddHandler 的替换方法在代码中显式挂接事件处理程序,该方法有一个额外的标记,可以有效指出“即使事件被标记为已处理也可调用我。您用类似如下所示的调用指定该标 记:
m_SomeChildElement.AddHandler(UIElement.MouseDownEvent, (RoutedEventHandler)OnMouseDownCallMeAlways,true); AddHandler 的第一个参数是您想要处理的 RoutedEvent。第二个参数是对事件处理方法(它需要有事件委托的正确签名)的委托。第三个参数指明如果另一个处理程序已将事件标记为已处理,您 是否想得到通知。您调用 AddHandler 的元素就是在路由期间观察事件流动的元素。
路由事件和组合
现在我们来看一看 Button.Click 事件的形成过程,以了解为什么它如此重要。如前所述,用户将对 Button 可视树中的某些子元素(例如上一示例中的 Image)使用 MouseLeftButtonDown 事件启动 Click 事件。
在 Image 元素内发生 MouseLeftButtonDown 事件时,PreviewMouseLeftButtonDown 在根元素启动,然后沿隧道下行至 Image。如果没有处理程序为 Preview 事件将 Handled 标记设置为 True,MouseLeftButtonDown 即会从 Image 元素开始向上传播,直至到达 Button。按钮处理这一事件,将 Handled 标记设为 True,然后引发其自身的 Click 事件。本文中的示例代码包括一个应用程序,它带有整个路由链挂接的处理程序,可帮您查看这一进程。
其 蕴含的意义不可小视。例如,如果我选择通过应用包含 Ellipse 元素的控件模板替换默认按钮外观,可以保证在 Ellipse 外部单击即可触发 Click 事件。靠近 Ellipse 的外缘单击仍处于 my button 的矩形边界内,但 Ellipse 有其自身的 MouseLeftButtonDown 击中检测,而 Ellipse 外部按钮的空白区域则没有。
因 此,只有在 Ellipse 内部的单击才会引发 MouseLeftButtonDown 事件。它仍由附加此模板的 Button 类进行处理,所以,即便是自定义的按钮,您也能得到预测的行为。在编写自己自定义的复合控件时也需牢记这一非常重要的概念,因为您的操作很可能类似 Button 对控件内子元素的事件处理。
附加事件
为了让元素能处理在不同元素中声明的事件,WPF 支持附加事件。附加事件也是路由事件,它支持元素 XAML 形式的挂接,而非声明事件所用的类型。例如,如果您想要 Grid 侦听采用气泡方式通过的 Button.Click 事件,仅需按如下所示进行挂接即可。
<Grid Button.Click="myButton_Click">
<Button Name="myButton" >Click Me</Button>
</Grid>
在编译时生成的局部类中的最终代码现在如下所示:
#line 5 "....Window1.xaml" ((System.Windows.Controls.Grid)(target)).AddHandler( System.Windows.Controls.Primitives.ButtonBase.ClickEvent, new System.Windows.RoutedEventHandler(this.myButton_Click));附加事件可在挂接事件处理程序位置方面给予您更大的灵活性。但如果元素包含在同一类中(如本例所示),其差异并不会显露出来,这是由于处理方法针对的仍是 Window 类。
它 在两方面产生影响。第一,事件处理程序根据处理元素在气泡或隧道元素链中的位置进行调用。第二,您可额外执行一些操作,如从所用控件内封装的对象处理事 件。例如,您可以象处理 Grid 中所示的事件一样处理 Button.Click 事件,但这些 Button.Click 事件可以从窗口中包含的用户控件内部向外传播。
提示:事件处理程序命名
如果您不想一味使用事件处理程序的默认命名约定(objectName_eventName),仅需输入您需要的事件处理程序名称,右键单击,然后单击上下文菜单中的“浏览到事件处理程序即可。Visual Studio 随即按指定的名称生成事件处理程序。
在 Visual Studio 2008 SP1 中,“属性窗口会有一个事件视图,它与 Windows 窗体中的视图类似,因此如果您有 SP1,即可以在那里指定事件名称。但如果您采用的是 XAML,这是生成显式命名的处理程序的便捷方法。
生成事件处理程序(单击图像可查看大图)
并非所有事件都声明为附加事件。实际上,大部分事件都不是这样。但当您需要在控件来源之外处理事件时,附加事件会提供相当大的帮助。