路由事件
XAML不仅继承了传统的事件处理方式,还引入了一个增强型事件处理机制:路由事件(RoutedEvent)。路由事件和传统事件的不同是:路由事件允许一个对象触发事件后,可以同时拥有多个事件接收者。也就是说,路由事件可以针对多个对象(而不是仅针对触发该事件的对象)调用事件处理程序。
在XAML文件中,所有的元素对象构成一种嵌套结构,当应用程序运行时,也将按照层次结构顺序由外到内对这些元素对象进行初始化,最终生成一个对象树,在Windows应用商店的空白应用程序项目中,最终生成一个以Page元素为根的对象树。基于对象树的概念,XAML中路由事件处理方式可分为以下三种:
(1)冒泡路由方式。一个对象触发事件后,事件将沿着对象树由下至上,由子元素对象到父元素对象传播扩散,直到到达对象树的根元素,或者该事件的Handled属性取值为true时,完成处理。在传播扩散中,所有涉及的元素对象都会调用相应的事件处理程序。
(2)隧道路由方式。该类事件处理方式和冒泡方式相反,在对象触发事件后,事件将从根对象传播扩散到触发该事件的对象,或者该事件的Handled属性取值为 true时,完成处理。
(3)直接路由方式。在这种处理方式中事件不进行向上或向下传播扩散,仅作用于触发该事件的当前对象上。
下面以冒泡路由方式为例来演示路由事件的处理过程。新建一个Windows应用商店的空白应用程序项目,将其命名为BubblingRoutedEventApplication,打开项目下的MainPage.xaml文件,在Grid元素中添加如下代码:
<Grid PointerPressed="Grid_PointerPressed" Height="250" Width="300" Background="Black">
<TextBlock Text="Grid控件" FontWeight="Bold" Margin="5"/>
<Canvas Background="Gray" PointerPressed="Canvas_PointerPressed" Margin="29,30,25,35">
<TextBlock Text="Canvas控件" FontWeight="Bold" Foreground="White" Margin="5"/>
<StackPanel Height="117" Width="196" Background="Black" Canvas.Top="30" Canvas.Left="26" PointerPressed="StackPanel_PointerPressed">
<TextBlock Text="StackPanel控件" FontWeight="Bold" Margin="5"/>
<TextBlock Text="事件冒泡顺序是:" FontWeight="Bold" Margin="5"/>
<TextBox Name="ShowEventOrder" BorderBrush="Gray" Background="Black" FontSize="10" Foreground="White" TextWrapping="Wrap" Margin="10,0" Height="65"/>
</StackPanel>
</Canvas>
</Grid>
在上面的代码中,添加了一个Grid控件,设置背景色为黑色,并为PointerPressed事件注册了处理方法Grid_PointerPressed,同时在该控件内部添加了一个TextBlock控件和一个Canvas控件, 其中TextBlock控件的文本内容为"Grid控件",Canvas控件的背景色为灰色,并为Canvas控件的PointerPressed事件注册了处理方法Canvas_PointerPressed。接着在Canvas控件内又添加了一个TextBlock控件和一个StackPanel控件,TextBlock控件的文本内容为"Canvas控件",文本颜色为白色,StackPanel控件的背景色为黑色,并为PointerPressed事件注册了处理方法StackPanel_PointerPressed。最后在StackPanel控件中,先添加了两个TextBlock文本块,其中一个文本块的文本内容为"StackPanel控件",另一个文本块的文本内容为"事件冒泡顺序是:",随后又定义了一个文本框,将其命名为ShowEventOrder,并分别设置文本框的边框颜色和字体颜色等。
布局好前台界面后,打开MainPage.xaml.cs文件,分别为Grid控件、Canvas控件和StackPanel控件的PointerPressed事件定义相应的事件处理方法。
Grid控件的PointerPressed事件处理方法Grid_PointerPressed的代码片段如下所示:
private void Grid_PointerPressed(object sender, PointerRoutedEventArgs e)
{
ShowEventOrder.Text += "Grid; ";
e.Handled = true;
}
在上面的代码中,将"Grid; "字符串追加到ShowEventOrder文本框中,并通过设置参数e的Handled属性值为true来停止事件传播。
Canvas控件的PointerPressed事件处理方法Canvas_PointerPressed的代码片段如下所示:
private void Canvas_PointerPressed(object sender, PointerRoutedEventArgs e)
{
// 将"Canvas - "字符串追加到ShowEventOrder文本框中
ShowEventOrder.Text += "Canvas - ";
}
StackPanel控件的PointerPressed事件处理方法StackPanel_PointerPressed的代码片段如下所示:
private void StackPanel_PointerPressed(object sender, PointerRoutedEventArgs e)
{
// 将"StackPanel - "字符串追加到ShowEventOrder文本框中
ShowEventOrder.Text += "StackPanel - ";
}
启动调试,未单击任何控件区域之前的效果如图3-13所示。
图3-13 界面初始状态
当单击Grid控件区域时,Grid对象会触发PointerPressed事件,并且调用相应的事件处理方法Grid_PointerPressed,完成将字符串"Grid; "追加到ShowEventOrder文本框中。这时,事件的Handled属性值已经为true,所以事件停止扩散,最终产生的结果如图3-14所示。
图3-14 单击Grid控件区域产生的结果
当单击Canvas控件区域时,Canvas对象会触发PointerPressed事件,并且调用相应的事件处理方法Canvas_PointerPressed,完成将字符串"Canvas- "追加到ShowEventOrder文本框中。由于Canvas对象还有一个父对象Grid,因此事件会继续向上传播,调用Grid的事件处理方法Grid_PointerPressed。事件传到Grid对象后就停止传播,最终产生的结果如图3-15所示。
图3-15 单击Canvas控件区域产生的结果
当单击StackPanel控件区域时,StackPanel对象会触发PointerPressed事件,并且调用相应的处理方法StackPanel_PointerPressed,完成将字符串"StackPanel - "追加到ShowEventOrder文本框中。由于StackPanel对象上面还有父对象,因此事件会继续向上传播,依次调用Canvas对象的事件处理方法Canvas_PointerPressed和Grid对象的事件处理方法Grid_PointerPressed。待事件传到Grid对象后,将停止传播,最终产生的结果如图3-16所示。
图3-16 单击StackPanel控件区域产生的结果