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 ...
随机推荐
- 如何在Apache HttpClient中设置TLS版本
1.简介 Apache HttpClient是一个底层.轻量级的客户端HTTP库,用于与HTTP服务器进行通信. 在本教程中,我们将学习如何在使用HttpClient时配置支持的传输层安全(TLS)版 ...
- SECURECRT 连接锐捷交换机CONSOLE
协议选择Serial,端口选择COM1.波特率设置为9600.RTS/CTS要把勾去掉(关闭流控功能)
- JavaScript实现拖放效果
JavaScript实现拖放效果 笔者实现该效果也是套用别人的轮子的.传送门 然后厚颜无耻的贴别人的readme~,笔者为了方便查阅就直接贴了,有不想移步的可以看这篇.不过还是最好请到原作者的GitH ...
- Attention和Transformer详解
目录 Transformer引入 Encoder 详解 输入部分 Embedding 位置嵌入 注意力机制 人类的注意力机制 Attention 计算 多头 Attention 计算 残差及其作用 B ...
- 流暢的python學習-3
一.文件操作 #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Created on Thu Apr 23 20:59 ...
- 第十三篇 -- 关于C++不支持int
如果是exe的程序的话,DWORD是非法字符,所以需要添加头文件"windows.h"
- ElasticSearch简介和快速实战
ElasticSearch简介和快速实战 ElasticSearch与Lucene Lucene可以被认为是迄今为止最先进.性能最好的.功能最全的搜索引擎库(框架) 但是想要使用Lucene,必须使用 ...
- vscode安装ESlint配置
先安装插件ESLint,后面在设置setting.json中配置加入代码: { "files.autoSave": "afterDelay", "ed ...
- 【LeetCode】34. 在排序数组中查找元素的第一个和最后一个位置
34. 在排序数组中查找元素的第一个和最后一个位置 知识点:数组,二分查找: 题目描述 给定一个按照升序排列的整数数组 nums,和一个目标值 target.找出给定目标值在数组中的开始位置和结束位置 ...
- 自学linux——6.安全外壳协议(ssh服务)
ssh服务 ssh(secure shell)安全外壳协议:远程连接协议,远程文件传输协议 1.协议使用端口号默认:22 若要修改,则修改ssh服务的配置文件/etc/ssh/ssh_config a ...