深入浅出WPF——附加事件(Attached Event)
3.3 事件也附加——深入浅出附加事件
WPF事件系统中还有一种事件被称为附加事件(Attached Event),简言之,它就是路由事件。“那为什么还要起个新名字呢?”你可能会问。
“身无彩凤双飞翼,心有灵犀一点通”,这就是对附加事件宿主的真实写照。怎么解释呢?让我们看看都有哪些类拥有附加事件:
- Binding类:SourceUpdated事件,TargetUpdated事件
- Mouse类:MouseEnter事件、MouseLeave事件、MouseDown事件、MouseUp事件,等等
- Keyboard类:KeyDown事件、KeyUp事件,等等
再对比一下那些拥有路由事件的类,诸如Button、Slider、TextBox……发现什么问题了吗?原来,路由事件的宿主都是些拥有可视化实体的界面元素,而附加事件则不具备显示在用户界面上的能力。也就是说,附加事件的宿主没有界面渲染功能这双“飞翼”,但一样可以使用附加事件这个“灵犀”与其他对象进行沟通。
理解了附加事件的原理,让我们动手写一个例子。我想实现的逻辑是这样的:设计一个名为Student的类,如果Student实例的Name属性值发生了变化就激发一个路由事件,我会使用界面元素来捕捉这个事件。
这个类的代码如下:
public class Student
{
// 声明并定义路由事件
public static readonly RoutedEvent NameChangedEvent = EventManager.RegisterRoutedEvent
("NameChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Student)); public int Id { get; set; }
public string Name { get; set; }
}
设计一个简单的界面:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Attached Event"
Height="" Width="">
<Grid x:Name="gridMain">
<Button x:Name="button1" Content="OK" Width="" Height=""
Click="Button_Click" />
</Grid>
</Window>
其后台代码如下:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent(); // 为外层Grid添加路由事件侦听器
this.gridMain.AddHandler(
Student.NameChangedEvent,
new RoutedEventHandler(this.StudentNameChangedHandler));
} // Click事件处理器
private void Button_Click(object sender, RoutedEventArgs e)
{
Student stu = new Student() { Id = , Name = "Tim" };
stu.Name = "Tom";
// 准备事件消息并发送路由事件
RoutedEventArgs arg = new RoutedEventArgs(Student.NameChangedEvent, stu);
this.button1.RaiseEvent(arg);
} // Grid捕捉到NameChangedEvent后的处理器
private void StudentNameChangedHandler(object sender, RoutedEventArgs e)
{
MessageBox.Show((e.OriginalSource as Student).Id.ToString());
}
}
后台代码中,当界面上唯一的Button被点击后会触发Button_Click这个方法。有一点必须注意的是:因为Student不是UIElement的派生类,所以它不具有RaiseEvent这个方法,为了发送路由事件就不得不“借用”一下Button的RaiseEvent方法了。在窗体的构造器中我为Grid元素添加了对Student.NameChangedEvnet的侦听——与添加对路由事件的侦听没有任何区别。Grid在捕捉到路由事件后会显示事件消息源(一个Student实例)的Id。
运行程序并点击按钮,效果如图:
理论上现在的Student类已经算是具有一个附加事件了,但微软的官方文档约定要为这个附加事件添加一个CLR包装以便XAML编辑器识别并进行智能提示。可惜的是,Student类并非派生自UIElement,因此亦不具备AddHandler和RemoveHandler两个方法,所以不能使用CLR属性作为包装器(因为CLR属性包装器的add和remove分支分别调用当前对象的AddHandler和RemoveHandler)。微软规定:
- 为目标UI元素添加附加事件侦听器的包装器是一个名为Add*Handler的public static方法,星号代表事件名称(与注册事件时的名称一致)。此方法接收两个参数,第一个参数是事件的侦听者(类型为DependencyObject),第二个参数为事件的处理器(RoutedEventHandler委托类型)。
- 解除UI元素对附加事件侦听的包装器是名为Remove*Handler的public static方法,星号亦为事件名称,参数与Add*Handler一致。
按照规范,Student类被升级为这样:
public class Student
{
// 声明并定义路由事件
public static readonly RoutedEvent NameChangedEvent = EventManager.RegisterRoutedEvent
("NameChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Student)); // 为界面元素添加路由事件侦听
public static void AddNameChangedHandler(DependencyObject d, RoutedEventHandler h)
{
UIElement e = d as UIElement;
if (e != null)
{
e.AddHandler(Student.NameChangedEvent, h);
}
} // 移除侦听
public static void RemoveNameChangedHandler(DependencyObject d, RoutedEventHandler h)
{
UIElement e = d as UIElement;
if (e != null)
{
e.RemoveHandler(Student.NameChangedEvent, h);
}
} public int Id { get; set; }
public string Name { get; set; }
}
原来的代码也需要做出相应的改动(只有添加事件侦听一处需要改动):
public Window1()
{
InitializeComponent(); // 为外层Grid添加路由事件侦听器
Student.AddNameChangedHandler(
this.gridMain,
new RoutedEventHandler(this.StudentNameChangedHandler));
}
现在让我们仔细理解一下附加事件的“附加”。确切地说,UIElement类是路由事件宿主与附加事件宿主的分水岭,不单是因为从UIElement类开始才具备了在界面上显示的能力,还因为RaiseEvent、AddHander和RemoveHandler这些方法也定义在UIElement类中。因此,如果在一个非UIElement派生类中注册了路由事件,则这个类的实例即不能自己激发(Raise)此路由事件也无法自己侦听此路由事件,只能把这个事件的激发“附着”在某个具有RaiseEvent方法的对象上、借助这个对象的RaiseEvent方法把事件发送出去;事件的侦听任务也只能交给别的对象去做。总之,附加事件只能算是路由事件的一种用法而非一个新概念,说不定哪天微软就把附加事件这个概念撤消了。
最后分享些实际工作中的经验。
第一,像Button.Click这些路由事件,因为事件的宿主是界面元素、本身就是UI树上是一个结点,所以路由事件路由时的第一站就是事件的激发者。附加事件宿主不是UIElement的派生类,所以不能出现在UI树上的结点,而且附加事件的激发是借助UI元素实现的,因此,附加事件路由的第一站是激发它的元素。
第二,实际上很少会把附加事件定义在Student这种与业务逻辑相关的类中,一般都是定义在像Binding、Mouse、Keyboard这种全局的Helper类中。如果需要业务逻辑类的对象能发送出路由事件来怎么办?我们不是有Binding吗!如果程序架构设计的好(使用数据驱动UI),那么业务逻辑一定会使用Binding对象与UI元素关联,一旦与业务逻辑相关的对象实现了INotifyPropertyChanged接口并且Binding对象的NotifyOnSourceUpdated属性设为true,则Binding就会激发其SourceUpdated附加事件,此事件会在UI元素树上路由并被侦听者捕获。
转载:http://blog.csdn.net/FantasiaX/article/details/4575968
相关:http://blog.csdn.net/fwj380891124/article/details/8139260
深入浅出WPF——附加事件(Attached Event)的更多相关文章
- WPF 附加事件
在WPF中有许多控件有他们自己的特殊的事件.按钮就是一个例子——它添加了 Click 事件,而其他任何类都没有定义该事件. 这回导致两难的境地.假设在 StackPanel 面板中包装了一堆按钮,并且 ...
- 《深入浅出WPF》笔记——事件篇
如果对事件一点都不了解或者是模棱两可的话,建议先去看张子阳的委托与事件的文章(比较长,或许看完了,也忘记看这一篇了,没事,我会原谅你的)http://www.cnblogs.com/JimmyZhan ...
- WPF 之路由事件和附加事件(六)
一.消息驱动与直接事件模型 事件的前身是消息(Message).Windows 是消息驱动的系统,运行其上的程序也遵循这个原则.消息的本质就是一条数据,这条消息里面包含着消息的类别,必要的时候还记 ...
- WPF 路由事件 Event Routing
原文:WPF 路由事件 Event Routing 1.路由事件介绍 之前介绍了WPF的新的依赖属性系统,本篇将介绍更高级的路由事件,替换了之前的.net普通事件.相比.net的事件,路由事件具有更强 ...
- 整理:WPF中应用附加事件制作可以绑定命令的其他事件
原文:整理:WPF中应用附加事件制作可以绑定命令的其他事件 目的:应用附加事件的方式定义可以绑定的事件,如MouseLeftButton.MouseDouble等等 一.定义属于Control的附加事 ...
- 《深入浅出WPF》 学习笔记
<深入浅出WPF> 序言 1. 什么是WPF 2. 为什么要学习WPF 第一章 XAML概览 1. XAML是什么? 2. XAML有哪些优点 第二章 从零起步认识XAML 1. 新 ...
- Ext JS学习第十六天 事件机制event(一) DotNet进阶系列(持续更新) 第一节:.Net版基于WebSocket的聊天室样例 第十五节:深入理解async和await的作用及各种适用场景和用法 第十五节:深入理解async和await的作用及各种适用场景和用法 前端自动化准备和详细配置(NVM、NPM/CNPM、NodeJs、NRM、WebPack、Gulp/Grunt、G
code&monkey Ext JS学习第十六天 事件机制event(一) 此文用来记录学习笔记: 休息了好几天,从今天开始继续保持更新,鞭策自己学习 今天我们来说一说什么是事件,对于事件 ...
- [转]深入浅出WPF(7)——数据的绿色通道,Binding
本文转自:http://liutiemeng.blog.51cto.com/120361/95273 小序: 怎么直接从2蹦到7啦?!啊哦,实在是不好意思,最近实在是太忙了,忙的原因也非常简单——自己 ...
- 深入浅出WPF笔记
数据层(Database,Oracle等) 业务逻辑层(Service,Data Access Layer,WCF) 表示层(WPF,Win Form,ASP.net,Silverlight) [WP ...
随机推荐
- Day-11: IO编程
由于CUP的运行速度远高于其他外设,IO操作有两种方式: 同步IO:CUP登着,程序暂停直到执行完后续代码 异步IO:CUP不等待,去做其他的事情,磁盘做完该做的事情后,告诉CUP,CUP再进行后续代 ...
- TensorBoard使用
关于TensorBoard的安装是在安装Tensorflow的过程中就已经默认安装好了,所以安装了Tensorflow就不需要再安装TensorBoard,直接使用就可以了. 具体的使用方法: 命令行 ...
- React——组件
一.创建组件 在React中有两种创建组件的方式,分别是函数形式的组件和类形式的组件 //函数形式: function Welcome(props){ return <p>this is ...
- yum仓库的定制
矮哥linux运维群: 93324526 笔者QQ:578843228 一.简介 软件包的分类.源码包脚本安装二进制包(rpm包.系统默认包) 源码包: C语言的源代码优点:开源,如果有能力,可以修改 ...
- jQuery高级Ajax
.load();加载远程的HTML文件代码,并插入到指定的DOM节点中.可以只传入一个参数,表示加载一个静态的HTML代码片段. $("#div1").load("loa ...
- 201521123082 《Java程序设计》第5周学习总结
201521123082 <Java程序设计>第5周学习总结 标签(空格分隔): java 1. 本周学习总结 1.1 尝试使用思维导图总结有关多态与接口的知识点. 1.2 可选:使用常规 ...
- 团队作业8——第二次项目冲刺(Beta阶段)Day1--5.18
1.展开站立式会议: 会议内容:①团队里的新成员做简单的自我介绍. ②讨论每天的任务分配问题. ③提出一个严格要求:博客一定要及时发布,并及时提交作业,遇到困难可以提出来一起解决. ④就现阶段的项目进 ...
- 201521123121 《Java程序设计》第12周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多流与文件相关内容. Java流(Stream).文件(File)和IO Java.io包几乎包含了所有操作输入.输出需要的类.所有 ...
- Android 跳转系统选择本地视频的功能
今天在项目开发的过程中产品要求添加选择本地视频的功能,于是就翻阅和查找各种资料,进行功能的开发,但是在开发过程中发现,各种不同的品牌的手机跳转至系统选择本地视频的功能结果不太一样,所以我就对一些主流的 ...
- 关于Java中数组的常用操作方法
1. 声明一个数组 String[] arr1 = new String[5]; String[] arr2 = {"a","b","c", ...