WPF路由事件二:路由事件的三种策略

一、什么是路由事件

路由事件是一种可以针对元素树中的多个侦听器而不是仅仅针对引发该事件的对象调用处理程序的事件。路由事件是一个CLR事件。

路由事件与一般事件的区别在于:路由事件是一种用于元素树的事件,当路由事件触发后,它可以向上或向下遍历可视树和逻辑树,他用一种简单而持久的方式在每个元素上触发,而不需要任何定制的代码(如果用传统的方式实现一个操作,执行整个事件的调用则需要执行代码将事件串联起来)。

路由事件的路由策略:

所谓的路由策略就是指:路由事件实现遍历元素的方式。

路由事件一般使用以下三种路由策略:1) 冒泡:由事件源向上传递一直到根元素。2) 直接:只有事件源才有机会响应事件。3) 隧道:从元素树的根部调用事件处理程序并依次向下深入直到事件源。一般情况下,WPF提供的输入事件都是以隧道/冒泡对实现的。隧道事件常常被称为Preview事件。

1、冒泡

XAML代码如下:

 1 <Window x:Class="WpfRouteEventByBubble.MainWindow"
 2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4         Title="MainWindow" Height="190" Width="246" WindowStartupLocation="CenterScreen">
 5     <Grid x:Name="GridRoot" Background="Lime">
 6         <Grid x:Name="GridA" Margin="10" Background="Blue">
 7             <Grid.ColumnDefinitions>
 8                 <ColumnDefinition></ColumnDefinition>
 9                 <ColumnDefinition></ColumnDefinition>
10             </Grid.ColumnDefinitions>
11             <Canvas x:Name="CanvasLeft" Grid.Column="0" Background="Red" Margin="10">
12                 <Button x:Name="ButtonLeft" Width="65" Height="100" Margin="10" Content="Left"></Button>
13             </Canvas>
14             <Canvas x:Name="CanvasRight" Grid.Column="1" Background="Yellow" Margin="10">
15                 <Button x:Name="ButtonRight" Width="65" Height="100" Margin="10" Content="Right"></Button>
16             </Canvas>
17         </Grid>
18     </Grid>
19 </Window>

运行效果如下所示:

当单击Left按钮的时候,Button.Click事件被触发,并且沿着ButtonLeft→CanvasLeft→GridA→GridRoot→Window这条路线向上传递,当单击Right按钮就会沿着ButtonRight→CanvasRight→GridA→GridRoot→Window这条路线向上传递,这里还没有添加监听器,所以是没有反应的。

如何加入监听器,我们可以再XAML中添加,XAML代码如下:

 1 <Window x:Class="WpfRouteEventByBubble.MainWindow"
 2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4         Title="MainWindow" Height="190" Width="246" WindowStartupLocation="CenterScreen">
 5     <Grid x:Name="GridRoot" Background="Lime" Button.Click="Button_Click">
 6         <Grid x:Name="GridA" Margin="10" Background="Blue" Button.Click="Button_Click">
 7             <Grid.ColumnDefinitions>
 8                 <ColumnDefinition></ColumnDefinition>
 9                 <ColumnDefinition></ColumnDefinition>
10             </Grid.ColumnDefinitions>
11             <Canvas x:Name="CanvasLeft" Grid.Column="0" Background="Red" Margin="10" Button.Click="Button_Click">
12                 <Button x:Name="ButtonLeft" Width="65" Height="100" Margin="10" Content="Left" Button.Click="Button_Click"></Button>
13             </Canvas>
14             <Canvas x:Name="CanvasRight" Grid.Column="1" Background="Yellow" Margin="10" Button.Click="Button_Click">
15                 <Button x:Name="ButtonRight" Width="65" Height="100" Margin="10" Content="Right" Button.Click="Button_Click"></Button>
16             </Canvas>
17         </Grid>
18     </Grid>
19 </Window>

我们在XAML代码中添加了Button.Click="Button_Click"这个事件处理器,就是监听器,并且事件处理交由Button_Click负责,后台Button_Click代码如下:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 using System.Windows;
 7 using System.Windows.Controls;
 8 using System.Windows.Data;
 9 using System.Windows.Documents;
10 using System.Windows.Input;
11 using System.Windows.Media;
12 using System.Windows.Media.Imaging;
13 using System.Windows.Navigation;
14 using System.Windows.Shapes;
15
16 namespace WpfRouteEventByBubble
17 {
18     /// <summary>
19     /// MainWindow.xaml 的交互逻辑
20     /// </summary>
21     public partial class MainWindow : Window
22     {
23         public MainWindow()
24         {
25             InitializeComponent();
26         }
27
28         private void Button_Click(object sender, RoutedEventArgs e)
29         {
30             MessageBox.Show("我到达了:" + (sender as FrameworkElement).Name);
31         }
32     }
33 }

我们分析一下,那两个参数到底是什么呢?

参数一:sender,这是听者,就是监听的地方,如果点击了Left按钮,那么Left按钮就会大声说:“我被点击了”这个事件向上传递,知道到了设有监听Button.Click事件的地方,这个地方就是sender。

参数二:是RoutEventArgs类型的,这个参数携带了一些重要信息,例如事件是从哪里来的,上一个传到哪里等,都可以利用这个参数来查询。

运行效果如下:

我们会发现,当点击button按钮时,ButtonLeft、CanvasLeft、GridA、GridRoot中的事件都会触发,这就是冒泡路由策略的功能所在,事件首先在源元素上触发,然后从每一个元素向上沿着树传递,直到到达根元素为止(或者直到处理程序把事件标记为已处理为止),从而调用这些元素中的路由事件。

如果把Button_Click事件修改为:

1  private void Button_Click(object sender, RoutedEventArgs e)
2  {
3         MessageBox.Show("我到达了:" + (sender as FrameworkElement).Name);
4         e.Handled = true;//让事件停止冒泡
5 }

则以上事件就不会沿着ButtonLeft→CanvasLeft→GridA→GridRoot→Window这条路线传递下去,只会执行ButtonLeft的事件。

二、管道

事件首先是从根元素上被触发,然后从每一个元素向下沿着树传递,直到到达根元素为止(或者直到到达处理程序把事件标记为已处理为止),他的执行方式正好与冒泡策略相反。

XAML代码如下;

1 <Window x:Class="Wpf路由事件管道策略.MainWindow"
2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4         Title="MainWindow" Height="350" Width="525" WindowStartupLocation="CenterScreen" PreviewMouseDown="Window_PreviewMouseDown">
5     <Grid x:Name="grid" PreviewMouseDown="grid_PreviewMouseDown">
6         <Button Height="30" Width="100" Content="点击我" PreviewMouseDown="Button_PreviewMouseDown"></Button>
7     </Grid>
8 </Window>

后台代码如下:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 using System.Windows;
 7 using System.Windows.Controls;
 8 using System.Windows.Data;
 9 using System.Windows.Documents;
10 using System.Windows.Input;
11 using System.Windows.Media;
12 using System.Windows.Media.Imaging;
13 using System.Windows.Navigation;
14 using System.Windows.Shapes;
15
16 namespace Wpf路由事件管道策略
17 {
18     /// <summary>
19     /// MainWindow.xaml 的交互逻辑
20     /// </summary>
21     public partial class MainWindow : Window
22     {
23         public MainWindow()
24         {
25             InitializeComponent();
26         }
27
28         private void Window_PreviewMouseDown(object sender, MouseButtonEventArgs e)
29         {
30             MessageBox.Show("windows被点击");
31         }
32
33         private void grid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
34         {
35             MessageBox.Show("grid被点击");
36         }
37
38         private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
39         {
40             MessageBox.Show("button被点击");
41         }
42     }
43 }

程序运行效果:

特别值得注意的是:管道事件按照惯例,他们的名字中都有一个preview前缀,一般来说管道事件都有他的配对的冒泡事件,例如:PreviewMouseDown和MouseDown就是配对事件,如果同时存在的话,那么就会先执行管道事件然后才执行配对的冒泡事件。当然e.Handled=true,依然能够阻断事件。

三、直接策略

事件仅仅在源元素上触发,这个与普通的.Net事件的行为相同,不同的是这样的事件仍然会参与一些路由事件的特定机制,如事件触发器等。

该事件唯一可能的处理程序是与其挂接的委托。

路由事件的事件处理程序的签名(即方法的参数):

他与通用的.net事件处理程序的模式一致,也有两个参数:第一个为:System.Object对象,名为sender,第二个参数(一般名为e)是一个派生于System.EventArgs的类。sender参数就是该处理程序被添加的元素,参数e是RoutedEventArgs的一个实例提供了4个有用的属性:

Source---逻辑树中开始触发该事件的的元素。

originalSource--可视树中一开始触发该事件的元素。

handled---布尔值,设置为true表示事件已处理,在这里停止。

RoutedEvent---真正的路由事件对象,(如Button.ClickEvent)当一个事件处理程序同时用于多个路由事件时,它可以有效地识别被出发的事件。

时间: 02-04

WPF路由事件二:路由事件的三种策略的相关文章

Struts2(二)action的三种方式

一.普通java类 package com.pb.web.action; /* * 创建普通的java类 */ public class HelloAction1 { public String execute(){ return "success"; } } 二.实现Action接口 package com.pb.web.action; import com.opensymphony.xwork2.Action; /* * 第二种实现Action接口 */ public class

白话经典算法系列之二 直接插入排序的三种实现

分类: 白话经典算法系列 2011-08-06 19:27 52070人阅读 评论(58) 收藏 举报 算法 直接插入排序(Insertion Sort)的基本思想是:每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序列中的适当位置,直到全部记录插入完成为止. 设数组为a[0…n-1]. 1.      初始时,a[0]自成1个有序区,无序区为a[1..n-1].令i=1 2.      将a[i]并入当前的有序区a[0…i-1]中形成a[0…i]的有序区间. 3.      i+

JavaScript--------------------jQuery中.bind() .live() .delegate() .on()的区别 和 三种方式写光棒事件 动画

bind(type,[data],fn) 为每个匹配元素的特定事件绑定事件处理函数. $("a").bind("click",function(){alert("ok");}); live(type,[data],fn) 给所有匹配的元素附加一个事件处理函数,即使这个元素是以后再添加进来的 $("a").live("click",function(){alert("ok");}); de

NGUI注册事件的三种方式

1.第一种方式 当一个元素要执行某个方法,而这个方法在此元素赋予的脚本上有,那么直接会调用此方法,但此方法的名称必须是内置的固定名称,例如OnClick,OnMouseOver,OnMouseOut等,不然会无法执行的! 至于原因,请看此链接:http://www.cnblogs.com/MrZivChu/p/event.html 2.第二种方式 为此元素添加Button Message脚本组件,参数Target表示目标对象,也就是说方法所在的脚本赋予在哪个对象上,Function Name表示

Spring学习(二)spring ioc注入的三种方式

一.spring ioc注入有哪三种方式: a setter 原理 : 在目标对象中,定义需要注入的依赖对象对应的属性和setter方法:"让ioc容器调用该setter方法",将ioc容器实例化的依赖对象通过setter注入给目标对象,封装在目标对象的属性中. b 构造器 原理 : 为目标对象提供一个构造方法,在构造方法中添加一个依赖对象对应的参数.ioc容器解析时,实例化目标对象时会自动调用构造方法,ioc只需要为构造器中的参数进行赋值:将ioc实例化的依赖对象作为构造器的参数传入

android开发中监听器的三种实现方法(OnClickListener)

Android开发中监听器的实现有三种方法,对于初学者来说,能够很好地理解这三种方法,将能更好地增进自己对android中监听器的理解. 一.什么是监听器. 监听器是一个存在于View类下的接口,一般以On******Llistener命名,实现该接口需要复写相应的on****(View v)方法(如onClick(View v)). 二.监听器的三种实现方法 (以OnClickListener为例) 方法一:在Activity中定义一个内部类继承监听器接口(这里是OnClickListener

WPF系列之二:解耦View层控件事件与ViewModel层事件的响应

以前的做法: 1.当项目的时间比较紧迫的时候,对UI层中控件的事件的处理,往往采取的是类似Winform中最简单的做法,直接做一个事件的Handler直接去调用VM层的方法. 2.控件只有一个Command属性,其它的事件的处理方法没有办法和ViewModel层进行解耦的时候往往也采取上面提到的方法. 如下图所示: 新的做法: 为了实现事件的处理与View层的解耦,我们可以利用WPF提供的附加属性来为需要的事件添加附加行为.附加属性扮演了一个在View层与Model层牵线的角色. 需要下面三个步

路由事件与附加事件区别

路由事件: 引发事件和处理CLR事件(注册和解除),都是由宿主自身处理的(this.RaiseEvent   this.AddHandler    this.RemoveHandler):宿主包含3个部分:声明并注册路由事件(XXXEvent).定义CLR事件包装器(XXX{add,remove}).封装包含引发路由事件代码的方法(OnFunction) 附加事件: 引发事件   和  处理CLR包装(注册和解除),都是由“目标元素”(即要安装该附加事件的那个元素)处理的(e.RaiseEven

AngularJS路由系列(2)--刷新、查看路由,路由事件和URL格式,获取路由参数,路由的Resolve

本系列探寻AngularJS的路由机制,在WebStorm下开发.主要包括: ● 刷新路由● 查看当前路由以及所有路由● 路由触发事件● 获取路由参数 ● 路由的resolve属性● 路由URL格式 项目文件结构 node_modules/ public/.....app/..........bower_components/...............toastr/....................toastr.min.css....................toastr.min