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 ...
随机推荐
- ELK多索引配置(filebeat)
nginx日志收集: #-------------------------输入 filebeat.inputs: #----------json日志---------- - type ...
- CF1025B题解
其他的几篇题解大多都是先求了 \(c_i \gets lcm(a_i,b_i)\) ,然后求全部 \(c_i\) 的最大公约数,但是对每一组数都求一下 \(lcm(a_i,b_i)\) 会增加时间复杂 ...
- NestJS WebSocket 开始使用
使用NestJs提供WebSocket服务. 本文会在新建项目的基础上增加2个类 Gateway 实现业务逻辑的地方 WebSocketAdapter WebSocket适配器 新建项目 新建一个项目 ...
- CF404D-DP【成就达成】
CF404D-DP 正经的东西 题意 给定一个字符串,只包含'0','1','2','*','?'五种字符,其中'?'可被替换为其他任何一种,求使序列符合扫雷地图定义的方案数. 一个数字字符大小表示与 ...
- 【Lucas组合数定理】组合-FZU 2020
组合 FZU-2020 题目描述 给出组合数C(n,m), 表示从n个元素中选出m个元素的方案数.例如C(5,2) = 10, C(4,2) = 6.可是当n,m比较大的时候,C(n,m)很大!于是x ...
- C++第四十四篇 -- MFC使用ChartCtrl绘制动态曲线
前言 目的:使用控制台程序带MFC类库画一个动态曲线图 参考链接: https://blog.csdn.net/sinat_29890433/article/details/105360032 htt ...
- 本地图片转base64编码
通常获取图片的base64编码都是通过input的上传file属性获取转化,但是有时候需要的是本地图片不经过上传操作,直接拿本地图片转成base64编码就不行了,input上传操作需要人为操作一下,没 ...
- vulnhub-DC:7靶机渗透记录
准备工作 在vulnhub官网下载DC:7靶机DC: 7 ~ VulnHub 导入到vmware,设置成NAT模式 打开kali准备进行渗透(ip:192.168.200.6) 信息收集 已经知道了靶 ...
- C++手写内存池
引言 使用new expression为类的多个实例分配动态内存时,cookie导致内存利用率可能不高,此时我们通过实现类的内存池来降低overhead.从不成熟到巧妙优化的内存池,得益于union的 ...
- openssl not found 离线安装的openssl问题
离线安装问题 正常我们在Linux中按照 nginx的openssl依赖都是通过 yum来安装的,但是由于一些特殊的服务器公司不让服务器连接互联网,所以就导致我们必须通过离线方式来进行安装,但是我们离 ...