深入浅出附加事件(转)

    铁猛兄的这篇文章基本上很透彻的讲解了附件事件的作用,尤其可以利用其用来在不同窗口间的事件捕捉,非常有效!
=================================================

 

事件也附加——深入浅出附加事件

                WPF事件系统中还有一种事件被称为附加事件(Attached Event),简言之,它就是路由事件。“那为什么还要起个新名字呢?”你可能会问。

                “身无彩凤双飞翼,心有灵犀一点通”,这就是对附加事件宿主的真实写照。怎么解释呢?让我们看看都有哪些类拥有附加事件:

  • Binding类:SourceUpdated事件,TargetUpdated事件
  • Mouse类:MouseEnter事件、MouseLeave事件、MouseDown事件、MouseUp事件,等等
  • Keyboard类:KeyDown事件、KeyUp事件,等等

再对比一下那些拥有路由事件的类,诸如ButtonSliderTextBox……发现什么问题了吗?原来,路由事件的宿主都是些拥有可视化实体的界面元素,而附加事件则不具备显示在用户界面上的能力。也就是说,附加事件的宿主没有界面渲染功能这双“飞翼”,但一样可以使用附加事件这个“灵犀”与其他对象进行沟通。

                理解了附加事件的原理,让我们动手写一个例子。我想实现的逻辑是这样的:设计一个名为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 { getset; }  
  8.     public string Name { getset; }          
  9. }  

 

 

         设计一个简单的界面:

 

        其后台代码如下:

 

 

 

                后台代码中,当界面上唯一的Button被点击后会触发Button_Click这个方法。有一点必须注意的是:因为Student不是UIElement的派生类,所以它不具有RaiseEvent这个方法,为了发送路由事件就不得不“借用”一下ButtonRaiseEvent方法了。在窗体的构造器中我为Grid元素添加了对Student.NameChangedEvnet的侦听——与添加对路由事件的侦听没有任何区别。Grid在捕捉到路由事件后会显示事件消息源(一个Student实例)的Id

                运行程序并点击按钮,效果如图:

 

 

                理论上现在的Student类已经算是具有一个附加事件了,但微软的官方文档约定要为这个附加事件添加一个CLR包装以便XAML编辑器识别并进行智能提示。可惜的是,Student类并非派生自UIElement,因此亦不具备AddHandlerRemoveHandler两个方法,所以不能使用CLR属性作为包装器(因为CLR属性包装器的addremove分支分别调用当前对象的AddHandlerRemoveHandler)。微软规定:

  • 为目标UI元素添加附加事件侦听器的包装器是一个名为Add*Handlerpublic static方法,星号代表事件名称(与注册事件时的名称一致)。此方法接收两个参数,第一个参数是事件的侦听者(类型为DependencyObject),第二个参数为事件的处理器(RoutedEventHandler委托类型)。
  • 解除UI元素对附加事件侦听的包装器是名为Remove*Handlerpublic static方法,星号亦为事件名称,参数与Add*Handler一致。

按照规范,Student类被升级为这样:

 

 

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

 

 

 

 

                现在让我们仔细理解一下附加事件的“附加”。确切地说,UIElement类是路由事件宿主与附加事件宿主的分水岭,不单是因为从UIElement类开始才具备了在界面上显示的能力,还因为RaiseEventAddHanderRemoveHandler这些方法也定义在UIElement类中。因此,如果在一个非UIElement派生类中注册了路由事件,则这个类的实例即不能自己激发(Raise)此路由事件也无法自己侦听此路由事件,只能把这个事件的激发“附着”在某个具有RaiseEvent方法的对象上、借助这个对象的RaiseEvent方法把事件发送出去;事件的侦听任务也只能交给别的对象去做。总之,附加事件只能算是路由事件的一种用法而非一个新概念,说不定哪天微软就把附加事件这个概念撤消了。

                最后分享些实际工作中的经验。

第一,像Button.Click这些路由事件,因为事件的宿主是界面元素、本身就是UI树上是一个结点,所以路由事件路由时的第一站就是事件的激发者。附加事件宿主不是UIElement的派生类,所以不能出现在UI树上的结点,而且附加事件的激发是借助UI元素实现的,因此,附加事件路由的第一站是激发它的元素。

第二,实际上很少会把附加事件定义在Student这种与业务逻辑相关的类中,一般都是定义在像BindingMouseKeyboard这种全局的Helper类中。如果需要业务逻辑类的对象能发送出路由事件来怎么办?我们不是有Binding吗!如果程序架构设计的好(使用数据驱动UI),那么业务逻辑一定会使用Binding对象与UI元素关联,一旦与业务逻辑相关的对象实现了INotifyPropertyChanged接口并且Binding对象的NotifyOnSourceUpdated属性设为true,则Binding就会激发其SourceUpdated附加事件,此事件会在UI元素树上路由并被侦听者捕获。

========================================

来源:http://blog.csdn.net/fantasiax/archive/2009/09/21/4575968.aspx

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

  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 { getset; }  
  28.     public string Name { getset; }  
  29. }  

  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. }  

  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>  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值