WPF 路由事件

每每谈到WPF的路由事件,我总是比较模糊的,因为我一般很少用,因为一般是用Binding来满足数据驱动界面的要求,要么就是通过路由命令来执行我想要做的方法,路由事件确实用得少,那么路由事件跟一般的事件的区别在哪里呢?如何使用呢?下面讲揭开其神秘的面纱:

首先说一说事件的历史,在windows操作系统上运行程序,都是消息驱动的,早期Windows API开发和MFC开发的时候可以直接看到各种消息,并且定义消息,到了VB和COM的时代,消息被封装成了事件,到了.NET时代也是事件,但是是直接式的,但了WPF时代又更灵活了,变成了路由式的了。那么到底什么是直接式,什么是路由式呢?

下面先直接切入主题,我们看一看路由事件到底是什么?直接的事件这里就不说了。

 1  <Grid>
 2         <Grid.ColumnDefinitions>
 3             <ColumnDefinition></ColumnDefinition>
 4             <ColumnDefinition></ColumnDefinition>
 5         </Grid.ColumnDefinitions>
 6         <Grid.RowDefinitions>
 7             <RowDefinition></RowDefinition>
 8             <RowDefinition></RowDefinition>
 9         </Grid.RowDefinitions>
10         <Grid x:Name="Grid1" Grid.Row="0" Grid.Column="0" Button.Click ="ButtonClicked">
11             <Grid.ColumnDefinitions>
12                 <ColumnDefinition></ColumnDefinition>
13                 <ColumnDefinition></ColumnDefinition>
14             </Grid.ColumnDefinitions>
15             <Grid x:Name="GridLeft" Grid.Column="0" Grid.Row="0">
16                 <Grid x:Name="GridLeftSub" Margin="5" Background="Blue">
17                     <Canvas x:Name="CanvasLeft" Margin="5" Background="Orange">
18                         <Button x:Name="ButtonLeft" Margin="5" Background="Red" Content="Left" Width="80" ></Button>
19                     </Canvas>
20                 </Grid>
21             </Grid>
22             <Grid x:Name="GridRight" Grid.Column="1" Grid.Row="0">
23                 <Grid x:Name="GridRightSub" Margin="5" Background="Blue">
24                     <Canvas x:Name="CanvasRight" Margin="5" Background="Orange">
25                         <Button x:Name="ButtonRight" Margin="5" Background="Red" Content="Right" Width="80" ></Button>
26                     </Canvas>
27                 </Grid>
28             </Grid>
29         </Grid>
30     </Grid>

如果点击ButtonLeft按钮,其Click事件会在其树上传递。
如果按照以前的思维,事件Click是Button的,那么谁负责响应呢?这里路由事件是给了外层的Grid来响应及订阅,能不能给其他的控件了,只要在这条路由通道树上都是可以的,那么也就是说,事件的应用者只负责激发事件,至于谁响应它自己并不知道,谁想响应谁就要去订阅事件,这个跟以前传统的事件是不一样了,为了区分以前传统的事件这里检查CLR事件,WPF的路由事件叫做路由事件。CLR事件的每对消息必须显示建立发送-响应关系。那么路由事件的优势到底在哪里?先不急,我们先自定义以下路由事件吧。

见代码:

 1  public class ReportTimeEventArgs:RoutedEventArgs
 2     {
 3         public DateTime ClickTime{get;set;}
 4         public ReportTimeEventArgs (RoutedEvent routedEvent,object source):base(routedEvent,source)
 5         {
 6
 7         }
 8     }
 9     public class TimeButton : Button
10     {
11         public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent("ReportTime",
12             RoutingStrategy.Bubble, typeof(EventHandler<ReportTimeEventArgs>), typeof(TimeButton));
13         public event RoutedEventHandler ReportTime
14         {
15             add { this.AddHandler(ReportTimeEvent, value); }
16             remove { this.RemoveHandler(ReportTimeEvent, value); }
17         }
18         protected override void OnClick()
19         {
20             base.OnClick();
21             ReportTimeEventArgs args = new ReportTimeEventArgs(ReportTimeEvent, this);
22             args.ClickTime = DateTime.Now;
23             this.RaiseEvent(args);
24         }
25     }

自定义一个时间按钮,注册的时候使用了EventManager.RegisterRoutedEvent函数,

第一个参数是指定事件的名称,一般要和CLR事件的名称一致。

第二个参数指定的是路由的策略,有三种,如果是Bubble,就是从里向外路由。如果是Tunnel就是由外向里路由,如果是Direct就是直达式,跟CLR事件的方式一样。

第三个参数指定的是事件处理器的类型。这里容易搞错,也就是说将来订阅这个事件的参数及返回值类型要跟这里指定的类型一致。这里要特别注意。就这一点我深入讲一下,先看看这几个委托及参数的原型:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

public delegate void EventHandler(object sender, EventArgs e);

public class RoutedEventArgs : EventArgs

public class ReportTimeEventArgs:RoutedEventArgs

如果可以直接使用EventArgs,那么就可以直接使用EventHandler委托类型,如果不是那就要使用泛型委托了,这就是我以前容易犯错的一个地方。

第四个参数就是指定事件的拥有者是什么类型。

在注册以后,就得到了一个路由事件,这个路由事件是public static readonly修饰。

跟依赖属性一样,注册完后,要CLR包装,不同的地方是这里包装是用add, remove。

包装完了外界就可以通过这个CLR包装事件来进行订阅。

接下来就是如何触发这个事件,于是我们重载了OnClick方法,在这个方法里面RaiseEvent,这个就是触发事件。

然后再讲讲路由事件的参数,我们事件的源头到底是哪个呢?sender又是谁呢?下面做一个实验:

自定义了一个控件:

1 <UserControl x:Class="WPFRoutedEventDemo.UserControl1"
2              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4              >
5     <Border BorderBrush="Blue" BorderThickness="13" CornerRadius="5">
6         <Button x:Name="innerButton" Content="OK"></Button>
7     </Border>
8 </UserControl>

然后在主窗体添加:

1  <Grid x:Name="gridFirst" Grid.Row="1" Button.Click ="ButtonClicked2">
2             <Grid x:Name="gridSecond">
3                 <local:UserControl1 x:Name="btn1" Margin="30"></local:UserControl1>
4             </Grid>
5         </Grid>

事件响应函数:

1  private void ButtonClicked2(object sender, RoutedEventArgs e)
2         {
3             string originSourceMsg = string.Format("OriginalSource is {0}, Name is:{1}", e.OriginalSource, (e.OriginalSource as FrameworkElement).Name);
4             string sourceMsg = string.Format("Source is {0}, Name is:{1}", e.Source, (e.Source as FrameworkElement).Name);
5             string senderMsg = string.Format("sender is {0}", sender.GetType());
6             MessageBox.Show(originSourceMsg +"\r\n" + sourceMsg + "\r\n" + senderMsg);
7         }

发现得到的结果如下:

我们发现如果调用e.OriginalSource,得到的就是VisualTree上的源头,如果调用的是e.Source得到的就是LogicalTree上的源头,

另外sender表示谁订阅.

还有一个就是e.Handled,如果设置为true,表示整个事件不在路由下去了.

到目前为止,我们知道了路由事件是如何创建的,如何控制路由,以及与传统事件的一个区别。那我们再看看一个特例,我们看看按钮有哪些事件,当然这里只是简单讲下,因为以前犯过迷糊:

首先我给一个按钮添加了三个事件处理程序,分别是PreviewMouseDown, Click, MouseDown;

 1   private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
 2         {
 3             MessageBox.Show("Button PreviewMouseDownEvent");
 4         }
 5
 6         private void Button_MouseDown(object sender, MouseButtonEventArgs e)
 7         {
 8             MessageBox.Show("Button MouseDownEvent");
 9         }
10
11         private void Button_Click(object sender, RoutedEventArgs e)
12         {
13             MessageBox.Show("Button ClickEvent");
14         }

然后我点击,发现只有PreviewMouseDown得到了响应。

为什么呢?

首先讲下public void AddHandler(RoutedEvent routedEvent, Delegate handler, bool handledEventsToo);这个函数:

特别是最后一个参数handledEventsToo:

如果为 true,则将按以下方式注册处理程序:即使路由事件在其事件数据中标记为已处理,也会调用该处理程序;

如果为 false,则使用默认条件注册处理程序,即当路由事件被标记为已处理时,将不调用处理程序。默认值为false。

一般带Preview前缀的事件都是隧道型事件,不带前缀的都是冒泡型事件,但是据我的查看,这些其实是有出入了,肉查看了UIElement的源码,发现很多事件都是Direct类型,也不知道为什么?

带着这些疑问,我再次做以下实验,并解决了这些问题:

xaml代码:

<Grid x:Name="gridTest" Grid.Row="1" Grid.Column="1">
            <Button x:Name="btnTest" Content="点我"></Button>

后台代码:

 1   gridTest.AddHandler(Button.PreviewMouseDownEvent, new RoutedEventHandler(PreviewEventHandler), true);
 2             gridTest.AddHandler(Button.PreviewMouseUpEvent, new RoutedEventHandler(PreviewEventHandler), true);
 3             gridTest.AddHandler(Button.PreviewMouseLeftButtonDownEvent, new RoutedEventHandler(PreviewEventHandler), true);
 4             gridTest.AddHandler(Button.PreviewMouseLeftButtonUpEvent, new RoutedEventHandler(PreviewEventHandler), true);
 5             gridTest.AddHandler(Button.ClickEvent, new RoutedEventHandler(PreviewEventHandler), true);
 6             gridTest.AddHandler(Button.MouseDownEvent, new RoutedEventHandler(PreviewEventHandler), true);
 7             gridTest.AddHandler(Button.MouseUpEvent, new RoutedEventHandler(PreviewEventHandler), true);
 8             gridTest.AddHandler(Button.MouseLeftButtonUpEvent, new RoutedEventHandler(PreviewEventHandler), true);
 9             gridTest.AddHandler(Button.MouseLeftButtonDownEvent, new RoutedEventHandler(PreviewEventHandler), true);
10
11             btnTest.AddHandler(Button.PreviewMouseDownEvent, new RoutedEventHandler(PreviewEventHandler), true);
12             btnTest.AddHandler(Button.PreviewMouseUpEvent, new RoutedEventHandler(PreviewEventHandler), true);
13             btnTest.AddHandler(Button.PreviewMouseLeftButtonDownEvent, new RoutedEventHandler(PreviewEventHandler), true);
14             btnTest.AddHandler(Button.PreviewMouseLeftButtonUpEvent, new RoutedEventHandler(PreviewEventHandler), true);
15             btnTest.AddHandler(Button.ClickEvent, new RoutedEventHandler(PreviewEventHandler), true);
16             btnTest.AddHandler(Button.MouseDownEvent, new RoutedEventHandler(PreviewEventHandler), true);
17             btnTest.AddHandler(Button.MouseUpEvent, new RoutedEventHandler(PreviewEventHandler), true);
18             btnTest.AddHandler(Button.MouseLeftButtonUpEvent, new RoutedEventHandler(PreviewEventHandler), true);
19             btnTest.AddHandler(Button.MouseLeftButtonDownEvent, new RoutedEventHandler(PreviewEventHandler), true);

事件响应代码:

1  private void PreviewEventHandler(object sender, RoutedEventArgs e)
2         {
3             Debug.WriteLine(string.Format("\t{0:mm:ss}\t{1}\tsender:{2}", DateTime.Now, e.RoutedEvent.Name,sender));
4         }     

输出结果如下:
03:44 PreviewMouseLeftButtonDown sender:System.Windows.Controls.Grid
 03:44 PreviewMouseDown sender:System.Windows.Controls.Grid
 03:44 PreviewMouseLeftButtonDown sender:System.Windows.Controls.Button: 点我
 03:44 PreviewMouseDown sender:System.Windows.Controls.Button: 点我
 03:44 MouseLeftButtonDown sender:System.Windows.Controls.Button: 点我
 03:44 MouseDown sender:System.Windows.Controls.Button: 点我
 03:44 MouseLeftButtonDown sender:System.Windows.Controls.Grid
 03:44 MouseDown sender:System.Windows.Controls.Grid
 03:44 PreviewMouseLeftButtonUp sender:System.Windows.Controls.Grid
 03:44 PreviewMouseUp sender:System.Windows.Controls.Grid
 03:44 PreviewMouseLeftButtonUp sender:System.Windows.Controls.Button: 点我
 03:44 PreviewMouseUp sender:System.Windows.Controls.Button: 点我
 03:44 Click sender:System.Windows.Controls.Button: 点我
 03:44 Click sender:System.Windows.Controls.Grid
 03:44 MouseLeftButtonUp sender:System.Windows.Controls.Button: 点我
 03:44 MouseUp sender:System.Windows.Controls.Button: 点我
 03:44 MouseLeftButtonUp sender:System.Windows.Controls.Grid
 03:44 MouseUp sender:System.Windows.Controls.Grid

看到结果我们再来一一分析原因:

哪些事件属于冒泡型:MouseLeftButtonDown, MouseDown,Click,MouseLeftButtonUp, MouseUp

哪些事件属于隧道型:PreviewMouseLeftButtonDown, PreviewMouseDown, PreviewMouseLeftButtonUp, PreviewMouseUp.

可以看出从实际情况上来看,确实带Preview的一般都是隧道型。

另外添加事件的时候,handledEventsToo设为True,并且Debug输出到输出窗口就能够把非preview的一些事件输出来,这个是为什么呢?

这个主要原因是在某些情况下,低级事件隐藏并转化为了高级事件,当我们把handledEventsToo设为True,就不会隐藏了,就能够输出来,而且也不能使用MessageBox,因为会阻塞消息循环,会导致后续的消息不会得到响应。

在这篇博客中有一点点涉及:

http://www.cnblogs.com/loveis715/archive/2012/04/10/2441513.html

Demo:

http://files.cnblogs.com/files/monkeyZhong/WPFRoutedEventDemo.zip

时间: 2024-08-06 11:58:37

WPF 路由事件的相关文章

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

一.什么是路由事件 路由事件是一种可以针对元素树中的多个侦听器而不是仅仅针对引发该事件的对象调用处理程序的事件.路由事件是一个CLR事件. 路由事件与一般事件的区别在于:路由事件是一种用于元素树的事件,当路由事件触发后,它可以向上或向下遍历可视树和逻辑树,他用一种简单而持久的方式在每个元素上触发,而不需要任何定制的代码(如果用传统的方式实现一个操作,执行整个事件的调用则需要执行代码将事件串联起来). 路由事件的路由策略: 所谓的路由策略就是指:路由事件实现遍历元素的方式. 路由事件一般使用以下三

[总结]使用WPF路由事件过程中遇到的一些小问题

写在前面 本文一开始会给出一个使用WPF路由事件的实例,因为本文所有的表述都将基于该实例.而本文所给实例来自于<WPF自定义路由事件>一文,在<WPF自定义路由事件>一文中会对实例代码做详细说明,所以,大家在阅读本文实例代码期间若存在疑问,可以先去看看<WPF自定义路由事件>一文,看是否能从中获得你想要的解答. 本文实例 1 新建DetailReportEventArgs类,该类派生自RoutedEventArgs类,RoutedEventArgs类包含与路由事件相关的

WPF路由事件学习转(二)

在传统的.net中已经有了事件机制了,为什么在WPF中要加入路由事件来取代事件呢,最直观的原因就是典型的WPF应用程序使用很多元素关联和组合起来,从而有了两个概念,LogicalTree 和 VisualTree,那么它们分别是什么呢,举个例子: 这就是LogicalTree,一个Grid里面镶嵌了其他控件或布局组件,这相当于一棵树中的叶子.而VisualTree呢?它就是一个树中的叶子里面的结构,用放大镜看一下,其实叶子里面的结构也是一颗树结构,这就是VisualTree了,例如 好了,既然W

理解WPF路由事件

(一)什么时路由事件功能定义:路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件.实现定义:路由事件是一个 CLR 事件,可以由 RoutedEvent 类的实例提供支持并由 Windows Presentation Foundation (WPF) 事件系统来处理(二)路由事件的划分(1)冒泡:针对事件源调用事件处理程序.路由事件随后会路由到后续的父元素,直到到达元素树的根.(2)隧道:最初将在元素树的根处调用事件处理程序.随后,路由事件将朝着路由事件

WPF路由事件一:逻辑树和可视树

一.什么是逻辑树 逻辑树就是描述WPF界面元素的实际构成,它是由程序在XAML中所有的UI元素组成.最显著的特点就是由布局控件.或者其他常用的控件组成. 1 <Window x:Class="WpfRouteEvent.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.c

WPF路由事件:逻辑树和可视树

一.什么是逻辑树 逻辑树就是描述WPF界面元素的实际构成,它是由程序在XAML中所有的UI元素组成.最显著的特点就是由布局控件.或者其他常用的控件组成. 1 <Window x:Class="WpfRouteEvent.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.c

WPF自定义路由事件

一 概要 本文通过实例演示WPF自定义路由事件的使用,进而探讨了路由事件与普通的CLR事件的区别(注:"普通的CLR事件"这个说法可能不太专业,但是,我暂时也找不到什么更好的称呼,就这么着吧,呵呵.)(扩展阅读:例说.NET事件的使用). 二 实例演示与说明 1 新建DetailReportEventArgs类,该类派生自RoutedEventArgs类,RoutedEventArgs类包含与路由事件相关的状态信息和事件数据.DetailReportEventArgs类中定义了属性Ev

WPF自定义路由事件(二)

WPF中的路由事件 as U know,和以前Windows消息事件区别不再多讲,这篇博文中,将首先回顾下WPF内置的路由事件的用法,然后在此基础上自定义一个路由事件. 1.WPF内置路由事件 WPF中的大多数事件都是路由事件,WPF有3中路由策略: 具体不多讲,单需要注意的是WPF路由事件是沿着VIsualTree传递的.VisualTree与LogicalTree的区别在于:LogicalTree的叶子节点是构成用户界面的控件(xaml紧密相关),而VisualTree要连控件中的细微结构也

WPF的路由事件、冒泡事件、隧道事件(预览事件)

原文:WPF的路由事件.冒泡事件.隧道事件(预览事件) 本文摘要: 1:什么是路由事件: 2:中断事件路由: 3:自定义路由事件: 4:为什么需要自定义路由事件: 5:什么是冒泡事件和预览事件(隧道事件): 1:什么是路由事件 WPF中的事件为路由事件,所谓路由事件,MSDN定义如下: 功能定义:路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件. 实现定义:路由事件是一个 CLR 事件,可以由 RoutedEvent 类的实例提供支持并由 Window