你的位置:首页 > ASP.net教程

[ASP.net教程]WPF学习之事件的学习(二)


  3.2自定义路由事件

  为了方便程序中对象之间的通信,通常需要我们自己定义一些路由事件。那么如何去创建自定义路由事件呢?下面通过一个例子来说明自定义路由事件的创建。

  创建自定义路由事件大体来说分为三个步骤:

  1. 声明并注册路由事件

    首先,定义路由事件与依赖属性的定义手法极为相似——申明一个由public static readonly修饰的RoutedEvent类型的字段,然后使用EventManager类的RegisterRoutedEvent方法进行注册。 完整的注册路由事件的代码如下:

  1. //声明并注册路由事件public static readonly RoutedEvent ClickEvent = EventManger.RegisterRoutedEvent("Click",RoutingStrategy.Bubble,typeof(RoutedEventHandler),typeof(ButtonBase));

    我们来分析一下 EventManger.RegisterRoutedEvent这个方法的四个参数。

      第一个参数是string类型的,被称为路由事件的名称。按照微软的建议,这个字符串RoutedEvent变量的前缀和CLR事件包装器的名称一致。再本例中,路由变量的名称为ClickEvent,则此字符串为Click。

      第二个参数为路由事件的策略,在WPF中,路由事件总共有三种策略:

    • Bubble,冒泡式:路由事件从激发它的容器开始,一层层向上传递,直至最外层容器(Window或者Page)。
    • Tunnel,隧道式:路由事件的传递方向和Bubble正好相反,是由UI树的树根向激发事件的控件移动
    • Direct,直达式:模仿CLR直接事件,将消息直接送达事件处理器

      第三个参数用于指定事件处理器的类型。事件处理器的返回值类型和参数列表必须与此参数指定的委托保持一致,不然会导致编译时抛出异常。

      第四个参数用于指明路由事件的宿主是哪个类型的。

  2. 为路由事件创建CLR事件包装

    为路由事件添加CLR事件包装是为了把路由事件暴露的很像一个传统的直接事件,如果不关注底层实现,程序员是不会感觉到它与传统的直接事件的区别。编程中仍然可以使用+=号为事件添加事件处理器和使用-=为事件移除不再使用的事件处理器。

  3. 创建可以激发路由事件的方法

    激发路由事件的方法很简单,首先创建一个需要事件携带的消息并把它与路由事件相关联,然后调用元素的RaiseEvent方法将事件发送出去。

 

  下面我们自己动手创建了一个路由事件,这个事件的用途是报告事件发生的时间。以此为例,说明自定义路由事件的创建方法。

  在上文中提到过,创建自定义路由事件有三个步骤,分别是声明并注册路由事件、为路由事件添加CLR的包装、激发路由事件。在本例中,我们要激发路由事件,首先得创建一个需要事件携带的消息。正所谓“兵马未动,粮草先行”,我们首先创建一个用于承载消息的事件参数。

  

 1 //用于承载时间消息的事件参数 2   class ReportTimeEventArgs : RoutedEventArgs 3   { 4     public DateTime ClickTime { get; set; } 5  6     public ReportTimeEventArgs(RoutedEvent routedEvrent,object source):base(routedEvrent,source) 7     { 8  9     }10   }

  然后,在创建一个Button类的派生类并按前述步骤为其添加路由事件:

  

 1 class TimeButton : Button 2   { 3     //声明、注册路由事件 4     public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent("ReportTime", RoutingStrategy.Bubble, typeof(EventHandler<ReportTimeEventArgs>), typeof(TimeButton)); 5  6     //CLR事件包装器 7     public event RoutedEventHandler ReportTime 8     { 9       add { this.AddHandler(ReportTimeEvent, value); }10       remove { this.RemoveHandler(ReportTimeEvent,value); }11     }12 13     //激发路由事件,借用Click事件的激发方法14 15     protected override void OnClick()16     {17       base.OnClick();18 19       ReportTimeEventArgs args = new ReportTimeEventArgs(ReportTimeEvent, this);20       args.ClickTime = DateTime.Now;21       this.RaiseEvent(args);22     }23   }

  下面是程序的界面XAML代码:

  

 1 <Window x:Class="_02_自定义路由事件.MainWindow" 2     ="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3     ="http://schemas.microsoft.com/winfx/2006/xaml" 4     ="http://schemas.microsoft.com/expression/blend/2008" 5     ="http://schemas.open 6     ="clr-namespace:_02_自定义路由事件" 7     mc:Ignorable="d" 8     Title="Routed Event" Height="300" Width="300" Name="window_1" 9     local:TimeButton.ReportTime="ReportTimeHandle">10   <Grid Name="grid_1" local:TimeButton.ReportTime="ReportTimeHandle">11     <Grid Name="grid_2" local:TimeButton.ReportTime="ReportTimeHandle">12       <Grid Name="grid_3" local:TimeButton.ReportTime="ReportTimeHandle">13         <StackPanel Name="stackPanel_1" local:TimeButton.ReportTime="ReportTimeHandle">14           <ListBox Name="listBox" Margin="10"></ListBox>15           <local:TimeButton x:Name="timeButton" Width="80" Height="80"16                    Content="报时" local:TimeButton.ReportTime="ReportTimeHandle">            17           </local:TimeButton>18         </StackPanel>19       </Grid>20     </Grid>21   </Grid>22 </Window>

  在UI界面上,以Window为根,套了三层Grid和一层StackPanel(在里面都设置了Name属性),在最内部的StackPanel里面放置了一个ListBox和TimeButton(也就是上面刚刚创建的Button类的派生类)。我们可以看出,从最外层的Window到最内层的TimeButton,都在监听者ReportTimeEvent 这个路由事件,并用ReportTimeHandle方法来响应这个事件。ReportTimeHandle的代码如下:

  

 1 //ReportTimeEvent路由事件处理器 2     private void ReportTimeHandle(object sender, ReportTimeEventArgs e) 3     { 4       FrameworkElement element = sender as FrameworkElement; 5       string timeStr = e.ClickTime.ToLongTimeString(); 6       string content = string.Format("{0}到达{1}", timeStr, element.Name); 7       this.listBox.Items.Add(content); 9    }

  运行程序,效果如下图所示:

      

  在这个例子中我们采用的路由事件的策略为冒泡式(bubble),我们对程序做个简单的修改,在声明和注册路由事件时,将其事件的策略改为隧道式(tunnel):

  

//声明、注册路由事件    public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent("ReportTime", RoutingStrategy.Tunnel, typeof(EventHandler<ReportTimeEventArgs>), typeof(TimeButton));

  效果如下图所示:

  

  对比两张效果图,我们可以很清楚的看到,两种不同的策略所带来不同,借此也能更好的理解之前所说的内容。

  这时候,大家有个疑问,如果让一个路由事件在某个地方被处理之后不再向后传呢?很简单,在RoutedEventArgs类或者其派生类的实例中,其具有一个bool类型的属性Handeled,一旦这个属性被设置为true,那么路由事件就不会再往下传递了。对应于我们这个例子,则需要做以下修改:

  

 1 //ReportTimeEvent路由事件处理器 2     private void ReportTimeHandle(object sender, ReportTimeEventArgs e) 3     { 4       FrameworkElement element = sender as FrameworkElement; 5       string timeStr = e.ClickTime.ToLongTimeString(); 6       string content = string.Format("{0}到达{1}", timeStr, element.Name); 7       this.listBox.Items.Add(content); 8  9       if (element == this.grid_2)10       {11         e.Handled = true;12       }13     }

  这样修改后的效果图如下:

    

    To Be Continue!