深入浅出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属性值发生了变化就激发一个路由事件,我会使用界面元素来捕捉这个事件。

这个类的代码如下:

1 public class Student
2 {
3     // 声明并定义路由事件
4     public static readonly RoutedEvent NameChangedEvent = EventManager.RegisterRoutedEvent
5         ("NameChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Student));
6
7     public int Id { get; set; }
8     public string Name { get; set; }
9 }

设计一个简单的界面:

1 <Window x:Class="WpfApplication1.Window1"
2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Attached Event"
4         Height="200" Width="200">
5     <Grid x:Name="gridMain">
6         <Button x:Name="button1" Content="OK" Width="80" Height="80"
7                 Click="Button_Click" />
8     </Grid>
9 </Window>

其后台代码如下:

 1 public partial class Window1 : Window
 2 {
 3     public Window1()
 4     {
 5         InitializeComponent();
 6
 7         // 为外层Grid添加路由事件侦听器
 8         this.gridMain.AddHandler(
 9             Student.NameChangedEvent,
10             new RoutedEventHandler(this.StudentNameChangedHandler));
11     }
12
13     // Click事件处理器
14     private void Button_Click(object sender, RoutedEventArgs e)
15     {
16         Student stu = new Student() { Id = 101, Name = "Tim" };
17         stu.Name = "Tom";
18         //  准备事件消息并发送路由事件
19         RoutedEventArgs arg = new RoutedEventArgs(Student.NameChangedEvent, stu);
20         this.button1.RaiseEvent(arg);
21     }
22
23     // Grid捕捉到NameChangedEvent后的处理器
24     private void StudentNameChangedHandler(object sender, RoutedEventArgs e)
25     {
26         MessageBox.Show((e.OriginalSource as Student).Id.ToString());
27     }
28 }

后台代码中,当界面上唯一的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类被升级为这样:

 1 public class Student
 2 {
 3     // 声明并定义路由事件
 4     public static readonly RoutedEvent NameChangedEvent = EventManager.RegisterRoutedEvent
 5         ("NameChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Student));
 6
 7     // 为界面元素添加路由事件侦听
 8     public static void AddNameChangedHandler(DependencyObject d, RoutedEventHandler h)
 9     {
10         UIElement e = d as UIElement;
11         if (e != null)
12         {
13             e.AddHandler(Student.NameChangedEvent, h);
14         }
15     }
16
17     // 移除侦听
18     public static void RemoveNameChangedHandler(DependencyObject d, RoutedEventHandler h)
19     {
20         UIElement e = d as UIElement;
21         if (e != null)
22         {
23             e.RemoveHandler(Student.NameChangedEvent, h);
24         }
25     }
26
27     public int Id { get; set; }
28     public string Name { get; set; }
29 }

原来的代码也需要做出相应的改动(只有添加事件侦听一处需要改动):

1 public Window1()
2 {
3     InitializeComponent();
4
5     // 为外层Grid添加路由事件侦听器
6     Student.AddNameChangedHandler(
7         this.gridMain,
8         new RoutedEventHandler(this.StudentNameChangedHandler));
9 }

现在让我们仔细理解一下附加事件的“附加”。确切地说,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

时间: 2024-12-19 01:24:01

深入浅出WPF——附加事件(Attached Event)的相关文章

WPF附加事件定义

路由事件的宿主都是些拥有可视化实体的界面元素.而附加事件则不具备显示在用户界面上的能力.加入和移出附件事件的两个方法命名约定: 1.为目标UI元素加入附加事件侦听器的包装器是一个名为Add*Handler的public static方法. 星号代表事件名称,与注冊事件时的名称一致. 2.解除UI元素对附加事件侦听的包装器是名为Remove*Handler的public static方法,星号也是事件名称. 代码例如以下: public class Student { public int ID

《深入浅出WPF》学习笔记之深入浅出话事件

WPF的事件为路由事件,路由的环境为UI组件树(Visual Tree),Visual Tree由控件和控件的组成元素组成,事件可以在控件内部传递并处理.另一个树为Logical Tree,只包含布局控件和其他控件而不包括控件的组成元素.因此路由事件沿着Visual Tree传递. 传统.Net开发中的直接事件模型的缺点 事件拥有者和响应者必须建立订阅关系,如果想让事件向外层控件传递必须手动编写事件响应链,即每个控件都要订阅事件并向其他控件再次传递该事件. 路由事件 与依赖属性类似,每个路由事件

《深入浅出WPF》 学习笔记

<深入浅出WPF> 序言 1. 什么是WPF    2. 为什么要学习WPF 第一章 XAML概览 1. XAML是什么? 2. XAML有哪些优点 第二章 从零起步认识XAML 1. 新建WPF项目 2. 剖析最简单的XAML代码 第三章 系统学习XAML语法 1. XAML文档的树形结构 2. XAML中为对象属性赋值的语法 2.1 使用标签的Attribute为对象属性赋值 2.2 使用TypeConverter 2.3 属性元素 2.4 标记扩展(Markup Extensions)

[转]深入浅出WPF(7)——数据的绿色通道,Binding

本文转自:http://liutiemeng.blog.51cto.com/120361/95273 小序: 怎么直接从2蹦到7啦?!啊哦,实在是不好意思,最近实在是太忙了,忙的原因也非常简单——自己的技术太差了,还有很多东西要学呀.门里门外,发现专业程序员非常重要的一项技能是读别人写的代码,这项技能甚至比自己写代码更重要.Anstinus同学就是读代码的高手,我写的代码他看两眼就知道怎么回事了,并且能够立刻修改,而他的代码我读了好几天还不知道是怎么回事儿呢. 2到7之间是留给XAML语言基础的

深入浅出WPF笔记

数据层(Database,Oracle等) 业务逻辑层(Service,Data Access Layer,WCF) 表示层(WPF,Win Form,ASP.net,Silverlight) [WPF开发方法论] AS-IS:UI事件驱动程序运行(Win Form)--TO-BE:数据驱动程序运行并显示在UI上(WPF) XAML:WPF技术中专门用于设计UI的语言. 逻辑树(logical tree):不考虑控件内部的组成结构,只观察由控件组成的"树". 可视元素树(visual

WPF路由事件学习转(二)

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

附加事件

UIElement元素 安装 附加事件 的侦听器只有一种方法:附加事件的宿主调用“Add*Handle”(UIElement元素,事件处理方法的引用)(星号为附加事件名,不包括后缀Event).

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

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

《深入浅出WPF》学习笔记之深入浅出话Binding

Binding作为数据的桥梁,它的两端分别是Binding的源(Source)和目标(Target),用来把Source中的数据送到Target中,并把在Target中的改变返回到Source.一般情况,Binding的源是逻辑层的对象,目标是UI层的控件对象. Binding的基本使用方法 如果想让作为数据源的对象在发生更改时自动显示到界面上,数据源的对象需要实现INotifyPropertyChanged接口.设置绑定时需要指定绑定到数据源的哪个属性,这个属性称为Binding的路径Path