WPF原理剖析——路由事件
一、路由事件与传统事件
传统事件的触发者和处理者是紧密相连的,而路由事件则不是,路由事件允许一个元素的事件有另外的元素触发。
也即就是说路由事件的拥有者和响应者之间没有显示的订阅关系。事件的拥有者只负责激发事件,事件将由谁处理他们并不知道(可以只引发事件没有响应,但是如果这个在传统事件里面的话是会产生异常的。),
1、事件触发者

//这个类负责事件的定义和触发
class MailManager
{
//第二步:定义事件成员
public event EventHandler<NewMailEventArgs> NewMail;
//第三步:定义一个负责引发事件的方法通知已经登记的对象
protected virtual void OnNewMail(NewMailEventArgs e)
{
// if (NewMail != null)//如果我们没有在引发事件的时候订阅事件,会产生异常(未将对象的引用设置到实例)
NewMail(this, e);
}
//第四步:将输入转化为期望事件,这里其实是对第三步的一个包装
public void RaiseEvent(string from, string to, string subject)
{
NewMailEventArgs e = new NewMailEventArgs(from, to, subject);
OnNewMail(e);
}
}

2、事件响应者

class Program
{
static void Main(string[] args)
{
MailManager mailManager = new MailManager();
//订阅事件
//mailManager.NewMail += new EventHandler<NewMailEventArgs> (mailManager_NewMail);//假设我们在引发事件之前没有订阅事件(相当于实例化一个事件)
//调用方法引发事件
mailManager.RaiseEvent("a", "b", "事件邮件");
Console.ReadKey();
}
//处理事件
static void mailManager_NewMail(object sender, NewMailEventArgs e)
{
Console.WriteLine("收到一封主题为{0},是从{1}到{2}", e.Subject, e.From, e.To);
}
}

大家可以看到我在编写传统事件的时候在事件响应者中去掉了事件的订阅,然后我直接引发了事件,这样就会产生一个“未将对象引用设置到对象的实例”,因为我们的事件是一个用Event修饰委托,而委托又会在编译的时候生成一个类。当我们没有事件订阅者的时候相当于,我们声明了一个类,但是没有创建他的实例,就开始调用它的实例方法。
例如我们有一个类A,里面包含一个方法Add(),
public A a;//声明类A
//a=new A();//这里我们直接省去这一步(即在事件过程中缺少事件的订阅)
a.Add();//这里我们调用Add方法的时候就会产生异常
通过上面的例子大家肯定明白了路由事件的触发者和响应者之间没有显示的订阅关系,这句话。
二、路由事件的注册(例子来源深入浅出WPF)
第一步:创建一个继承至RoutedEventArgs类的路由事件参数类,以便传递给事件响应着一些参数信息;如果我们没有什么其他额外信息需要传递的话,我们可以直接使用RoutedEventArgs这个路由事件参数基类。
public class ReportTimeEventArgs:RoutedEventArgs
{
public DateTime ClickTime { get; set; }
public ReportTimeEventArgs(RoutedEvent routedEvent, object source)
: base(routedEvent, source) { }
}
第二步:注册路由事件,并且添加普通事件包装器和事件激发方法。

/// <summary>
/// 创建一个报时按钮
/// </summary>
public class TimeButton:Button
{
//声明和注册路由事件
public static readonly RoutedEvent ReportTimeEvent=
EventManager.RegisterRoutedEvent("ReportTime",RoutingStrategy.Bubble,typeof(EventHandler<ReportTimeEventArgs>),typeof(TimeButton));
//普通事件包装器
public event RoutedEventHandler ReportTime
{
add{this.AddHandler(ReportTimeEvent,value);}
remove{this.RemoveHandler(ReportTimeEvent,value);}
}
//激发路由事件,借用Click事件的激发方法
protected override void OnClick()
{
base.OnClick();
ReportTimeEventArgs args=new ReportTimeEventArgs (ReportTimeEvent,this);//这个事件参数初始化的时候包含的参数提供了事件的来源
args.ClickTime=DateTime.Now;
this.RaiseEvent(args);//激发事件,这里和普通事件不同(普通事件激发是有事件包装器激发的)
}
}

Note:
1、路由事件注册方法RegisterRoutedEvent方法和注册依赖属性的方法很类似,他需要4个参数,普通事件包装器的名字、路由事件策略、事件处理类型、事件拥有者;
2、事件包装器,这里的目的主要是为了适应以前学习.net同学的方便这样我们就可以使用+=或者-=,我们其实可以直接调用AddHandler或者RemoveHandler方法来添加和删除事件;
3、激发事件方法调用的是RaiseEvent(),这个方法是在UIElement类中定义的,即所有UI元素都有这个方法。
三、路由事件的策略
1、Bubble(冒泡模式):事件从自己激发一直传递到根元素;
2、Tunnel(隧道模式):事件从根元素传递到自己;
3、Direct(直接模式):和传统事件一样。

<Window Name="wimdow1">
<Grid x:Name="grid1" local:TimeButton.ReportTime="ReportTimeHandler">
<Grid x:Name="grid2" local:TimeButton.ReportTime="ReportTimeHandler">
<Grid x:Name="grid3" local:TimeButton.ReportTime="ReportTimeHandler">
<StackPanel x:Name="stackPanel" local:TimeButton.ReportTime="ReportTimeHandler">
<ListBox x:Name="listBox"/>
<local:TimeButton x:Name="timeButton" Width="80" Height="80" Content="报时" local:TimeButton.ReportTime="ReportTimeHandler" ></local:TimeButton>
</StackPanel>
</Grid>
</Grid>
</Grid>
</Window>

上面代码我为每一个空间都设置了对TimeButton事件的侦听;所以当事件在上面传递的时候,都可以处理。下面是三种模式的输出结果:
Bubble(冒泡)
Tunnel(隧道)

Direct(直接)

四、路由事件中的几个注意字段
sender:这个是事件处理器中方法的触发源控件,他是当前控件而不是事件的最开始的发生源控件;
OriginalSource:这个是VisualTree树上的最开始发生源控件;
Source:这个是LogicTree树上的最开始发生源的控件。
五、附加事件
其实在上面我们就使用了附加事件,例如我们在给除TimeButton控件之外的空间都附加上了TimeButton控件的ReportTime事件;这样子的一个好处可以是我们统一处理一些控件的事件,例如我们一个stackpanel包含了多个button控件;这时候我们就可以将他们的click事件统一由stackpanel控件来处理,但是stackPanel又没有click事件,这时候我们就可以使用button.Click=""附加给stackpanel控件。其实这里也是WPF中路由事件的一种应用。我们不需要为每一个button控件都包含click事件的处理器,而可以统一由路由事件经过的visualtree上其他元素来处理。
WPF原理剖析——路由事件的更多相关文章
- WPF 学习笔记 路由事件
1. 可传递的消息: WPF的UI是由布局组建和控件构成的树形结构,当这棵树上的某个节点激发出某个事件时,程序员可以选择以传统的直接事件模式让响应者来响应之,也可以让这个事件在UI组件树沿着一定的方向 ...
- WPF学习之路由事件
原文:http://www.cnblogs.com/lxy131/archive/2010/08/10/1796754.html WPF中新添加了一种事件---路由事件 路由事件与一般事件的区别在于: ...
- WPF中的路由事件(转)
出处:https://www.cnblogs.com/JerryWang1991/archive/2013/03/29/2981103.html 最近因为工作需要学习WPF方面的知识,因为以前只关注的 ...
- WPF中自定义路由事件
public class MyButtonSimple: Button { // Create a custom routed event by first registering a RoutedE ...
- WPF 中的 路由事件
public class ReportTimeEventArgs:RoutedEventArgs { public ReportTimeEventArgs(RoutedEvent routedEven ...
- WPF手动触发路由事件
MouseButtonEventArgs args = , MouseButton.Left); args.RoutedEvent = UIElement.MouseLeftButtonDownEve ...
- WPF教程六:理解WPF中的隧道路由和冒泡路由事件
WPF中使用路由事件升级了传统应用开发中的事件,在WPF中使用路由事件能更好的处理事件相关的逻辑,我们从这篇开始整理事件的用法和什么是直接路由,什么是冒泡路由,以及什么是隧道路由. 事件最基本的用法 ...
- .NET: WPF 路由事件
(一)使用WPF内置路由事件 xaml: <Window x:Class="WpfApplication1.MainWindow" xmlns="http://sc ...
- 【WPF】路由事件
总结WPF中的路由事件,我将学到的内容分为四部分来逐渐掌握 第一部分:wpf中内置的路由事件 以Button的Click事件来说明内置路由事件的使用 XAML代码: <Window x:Clas ...
随机推荐
- 如何使用powershell操作json对象
读取Json文件 $dataTransformerDirPath为读取Json文件的目录 点击查看代码 # 读取文件 UTF8-NOBOM function ReadFile($path) { ret ...
- 12、Java——异常
1.异常处理方案: 异常抛出 : throws 把异常抛出到上一层,谁调用谁解决 异常捕获 : try..catch try{ 有可能出现异常的代码; }catch(FileNotFoundEx ...
- c++中的基本IO
引言 c++不直接处理输入和输出,而是通过标准库中的类型处理IO.IO的设备可以是文件.控制台.string.c++主要定义了三种IO类型,分别被包含在iostream.fstream.sstream ...
- Linux安装Tomcat-Nginx-FastDFS-Redis-Solr-集群——【第九集-补充-热部署项目到tomcat中,但是数据库配置文件错误,中途停止部署,导致执行shutdow.sh报错异常: Could not contact localhost:8005. Tomcat may not be running error while shutting down】
1,经过千辛万苦的尝试和百度,终于一个博客:http://stackmirror.caup.cn/page/skxugjqj0ldc关于catalina.sh文件的执行引起了我的注意: 2,我执行ca ...
- python+API接口测试框架设计(unittest)
1.测试框架简介 整个接口测试框架的设计图如下: basepage:存放的是公共的方法 common:存放的是自定义工具 data:存放的是公共动态数据,如BB.xls/ Id.md log:存放的是 ...
- [HAOI2012]外星人 题解
人类智慧题. 首先,只有 \(\varphi(1)=\varphi(2)=1\).再考虑题目中给的提示: \[\varphi\left(\prod_{i = 1}^m p_i^{q_i}\right) ...
- Navicat Premium 12安装及破解
特别提醒,Navicat Premium 12安装包请用我给的链接下载,不然会无法破解 下载Navicat Premium 12地址:https://pan.baidu.com/s/1AQsryKpJ ...
- C++ Socket编程(基础)
一.基本简介 在计算机通信领域,socket 被翻译为"套接字",它是计算机之间进行通信的一种约定或一种方式. 通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也 ...
- 第二十七篇 -- 如何给静态文本设置成ico图标形式以及如何修改文本框中的内容
修改静态文本框内容: CWnd* pWnd = GetDlgItem(IDC_STATIC1); pWnd->SetWindowText(_T("Server is on!" ...
- videojs踩过的坑
1.videojs-contrib-hls 在webpack中不能用,解决方法:window.videojs = videojs:出处 https://github.com/videojs/video ...