WPF自定义路由事件

一 概要

本文通过实例演示WPF自定义路由事件的使用,进而探讨了路由事件与普通的CLR事件的区别(注:“普通的CLR事件”这个说法可能不太专业,但是,我暂时也找不到什么更好的称呼,就这么着吧,呵呵。)(扩展阅读:例说.NET事件的使用)。

二 实例演示与说明

1 新建DetailReportEventArgs类,该类派生自RoutedEventArgs类,RoutedEventArgs类包含与路由事件相关的状态信息和事件数据。DetailReportEventArgs类中定义了属性EventTime和EventPublishr,EventTime属性记录事件的发生时间,而EventPublishr属性记录事件的发布者。

DetailReportEventArgs类的详细代码如下所示,类文件为DetailReportEventArgs.cs。

//************************************************************
//
// WPF路由事件示例代码
//
// Author:三五月儿
//
// Date:2014/08/31
//
// http://blog.csdn.net/yl2isoft
//
//************************************************************  

using System;
using System.Windows;

namespace WpfRoutedEventExp
{
    public class DetailReportEventArgs : RoutedEventArgs
    {
        public DetailReportEventArgs(RoutedEvent routedEvent, object source)
            :base(routedEvent,source){}
        public DateTime EventTime { get; set; }
        public string EventPublisher { get; set; }
    }
}

2 新建DetailReportButton类,该类派生自Button类,类中定义路由事件DetailReportEvent。

DetailReportButton类的详细代码如下所示,类文件为DetailReportButton.cs。

//************************************************************
//
// WPF路由事件示例代码
//
// Author:三五月儿
//
// Date:2014/08/31
//
// http://blog.csdn.net/yl2isoft
//
//************************************************************
using System;
using System.Windows;
using System.Windows.Controls;

namespace WpfRoutedEventExp
{
    public class DetailReportButton : Button
{
         //定义路由事件
        public static readonly RoutedEvent DetailReportEvent = EventManager.RegisterRoutedEvent("DetailReport",RoutingStrategy.Bubble,typeof(EventHandler<DetailReportEventArgs>),typeof(DetailReportButton));
        //CLR事件包装
        public event RoutedEventHandler  DetailReport
        {
            add { this.AddHandler(DetailReportEvent, value); }
            remove { this.RemoveHandler(DetailReportEvent, value); }
        }
        //事件触发方法
        protected override void OnClick()
        {
            base.OnClick();
            DetailReportEventArgs args = new DetailReportEventArgs(DetailReportEvent, this);
            args.EventPublisher = this.ToString();
            args.EventTime = DateTime.Now;
            this.RaiseEvent(args);
        }
    }
}

下面将结合代码来学习WPF路由事件的定义方法。

代码中先为类声明一个公共的(public)静态的(static)只读的(readonly)RoutedEvent类型的变量DetailReportEvent,然后使用EventManager类的RegisterRoutedEvent方法进行注册。

(1)先来说说RoutedEvent类的RegisterRoutedEvent方法

RoutedEvent类的RegisterRoutedEvent方法的定义如下所示:

public static RoutedEvent RegisterRoutedEvent(string name, RoutingStrategy routingStrategy, Type handlerType, Type ownerType);

从定义中可以看出,RegisterRoutedEvent方法接收四个参数,其中:

  • 第一个参数的类型为string,表示路由事件的名称,它必须和RoutedEvent类型变量的前缀一致。本例中RoutedEvent类型变量为DetailReportEvent,所以此处传入的name应该为DetailReport。
  • 第二个参数类型为RoutingStrategy,从字面意思就可以知道该参数表示路由事件的策略。WPF路由事件有3种路由策略:Bulle(冒泡式)、Tunnel(隧道式)和Direct(直达式),对于这三种策略在后面还会重点说明。此处先采用冒泡式,故传入参数为RoutingStrategy.Bubble。
  • 第三个参数与第四个参数的类型均为Type。其中:第三个参数指定事件处理器的类型,第四个参数指定路由事件的宿主类型。本例中的事件处理器类型为EventHandler<DetailReportEventArgs>,所以第三个传入参数为typeof(EventHandler<DetailReportEventArgs>)。路由事件的宿主为DetailReportButton 类,所以第四个传入参数为typeof(DetailReportButton)。

 (2)再来说说路由事件的触发方法OnClick

路由事件的触发在OnClick方法中完成,方法中先实例化DetailReportEventArgs 类,得到对象args,并为args的EventPublisher与EventTime属性赋值,这样就创建了携带有路由事件相关信息的对象。然后调用 RaiseEvent方法把事件消息发送出去。

(3)最后说说路由事件的CLR事件包装

在我们的实例中,还为路由事件添加了CLR事件包装,这样一来,路由事件就被包装成一个普通的CLR事件,我们可以像使用普通的CLR事件一样来使用它。可以通过“+=”与“-=”操作符来订阅事件和取消事件的订阅操作。

3 接下来,使用我们定义的DetailReportButton 类。

下面是程序的主画面,是不是极其简单啊,仅包含一个DetailReportButton类型的按钮。

图1 简洁的程序主画面

程序主画面的完整代码如下所示。

<Window x:Class="WpfRoutedEventExp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfRoutedEventExp"
        Title="MainWindow" Height="350" Width="525">
    <Grid x:Name="Grid_FirstLayer" >
        <Grid x:Name="Grid_SecondLayer" >
            <Grid x:Name="Grid_ThirdLayer" >
                <local:DetailReportButton x:Name="Button_Confirm" Width="100" Height="100" Content="Click Me" Margin="142,111,250,100" />
            </Grid>
        </Grid>
    </Grid>
</Window>

程序主画面的后端代码如下所示。

//************************************************************
//
// WPF路由事件示例代码
//
// Author:三五月儿
//
// Date:2014/08/31
//
// http://blog.csdn.net/yl2isoft
//
//************************************************************
using System.Windows;

namespace WpfRoutedEventExp
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.Grid_ThirdLayer.AddHandler(DetailReportButton.DetailReportEvent, new RoutedEventHandler(Button_Clicked1));
            this.Grid_SecondLayer.AddHandler(DetailReportButton.DetailReportEvent, new RoutedEventHandler(Button_Clicked1));
            this.Grid_FirstLayer.AddHandler(DetailReportButton.DetailReportEvent, new RoutedEventHandler(Button_Clicked1));
            this.Button_Confirm.AddHandler(DetailReportButton.DetailReportEvent, new RoutedEventHandler(Button_Clicked1));
        }

        private void Button_Clicked1(object sender, RoutedEventArgs e)
        {
            FrameworkElement ele = sender as FrameworkElement;
            DetailReportEventArgs dre = e as DetailReportEventArgs;
            MessageBox.Show(dre.EventPublisher + "-->" + ele.Name + ";" + dre.EventTime);
        }
    }
}

(1)我们的代码都干了些什么

主画面的代码中先通过代码xmlns:local="clr-namespace:WpfRoutedEventExp"引入名称空间,再使用代码local:DetailReportButton来初始化DetailReportButton 类。

主画面的后端代码中为控件Grid_ThirdLayer、Grid_SecondLayer、Grid_FirstLayer及Button_Confirm安装针对DetailReportButton的DetailReportEvent事件的侦听器,完成这个操作需要使用控件的AddHandler方法,该方法可以指定控件想侦听的事件,并将想侦听的事件与事件处理器关联起来。

本例中所有控件使用相同的事件处理器Button_Clicked1方法,Button_Clicked1方法中输出事件的发布者、事件的响应者以及事件到达的时间等信息。

程序的执行效果如下图所示。

图2 路由事件DetailReportEvent的执行效果图1

因为控件Button_Confirm、Grid_ThirdLayer、Grid_SecondLayer及Grid_FirstLayer均安装了针对DetailReportButton 的DetailReportEvent事件的侦听器,所以当路由事件DetailReportEvent到达Grid_ThirdLayer、Grid_SecondLayer、Grid_FirstLayer及Button_Confirm控件时都会被侦听到,进而执行事件处理方法Button_Clicked1,输出事件的发布者,此处为DetailReportButton,同时输出事件的侦听者,此处依次为Button_Confirm、Grid_ThirdLayer、Grid_SecondLayer及Grid_FirstLayer,并输出事件到达的时间。

(2)对我们的程序再来做些小修改

大家请看图2,注意事件到达的顺序,依次是Button_Confirm、Grid_ThirdLayer、Grid_SecondLayer及Grid_FirstLayer。

下面我们可以做个小实验,修改路由事件的策略为Tunnel(隧道式),再次执行程序,得到图3所示的执行效果。

给出执行效果前,先给出需要修改的代码:

public static readonly RoutedEvent DetailReportEvent = EventManager.RegisterRoutedEvent("DetailReport",RoutingStrategy.Tunnel,typeof(EventHandler<DetailReportEventArgs>),typeof(DetailReportButton));

图3 路由事件DetailReportEvent的执行效果图2

此刻!请睁大你的双眼,注意图3中的红色椭圆框,被红色框选中的是事件的响应者,他们出现的顺序是:Grid_FirstLayer、Grid_SecondLayer、Grid_ThirdLayer及Button_Confirm。

通过对比前后两次执行效果图,可以发现:当路由事件的路由策略被设置为Tunnel(隧道式)时,事件到达的顺序与将事件的路由策略设置为Bulle(冒泡式)时刚好相反。

(3)还没完,接着修改我们的代码

下面再看看将路由事件的路由策略修改为Direct(直达式)时的效果。

需要修改的代码为:

public static readonly RoutedEvent DetailReportEvent = EventManager.RegisterRoutedEvent("DetailReport",RoutingStrategy.Direct,typeof(EventHandler<DetailReportEventArgs>),typeof(DetailReportButton));

再次执行程序的效果如图4所示。

图4 路由事件DetailReportEvent的执行效果图3

当路由事件的路由策略被设置为Direct(直达式)时,效果与CLR事件一样,直接将事件消息传送给事件处理器。

(4)对路由事件的策略进行总结

当路由事件的路由策略被设置为Bulle(冒泡式)时,路由事件的消息会从事件的触发者开始向它的上级容器控件一层一层的往外传,直至最外层的容器控件;而将事件的路由策略设置为Tunnel(隧道式)时,效果刚好与冒泡式相反,从最外层容器一层一层往内传;对于直达式就不用多说了吧。

(5)该上主菜了吧,下面聊聊路由事件与普通CLR事件的区别

通过学习前面的实例演示,我们完全可以总结出路由事件与普通的CLR事件的区别。

普通的CLR事件通过事件订阅将事件的发布者与事件的订阅者紧密联系在一起,事件被触发时,事件发布者通过事件订阅将事件消息直接发送给事件订阅者,事件订阅者使用事件处理方法对事件的发生进行响应;而路由事件的发布者与响应者之间并不存在这种直接的订阅关系,事件的发布者只负责发布事件,而不用关心事件由谁来响应,因为事件发布者其实早就心知肚明,只有那些安装了事件侦听器的对象才会成为事件的响应者,至于谁愿意成为这个响应者他可不关心,而那些想侦听事件消息的对象只需要安装事件的侦听器,就可以侦听事件消息的达到,当事件消息到达时,使用事件处理方法进行响应。至于路由事件的消息采用何种方式来传递,取决于该路由事件采取何种路由策略。

(6)路由事件的优点(既然存在,必有道理)

降低了普通CLR事件中由于事件订阅而带来的耦合度。

三 总结

本文介绍了WPF自定义路由事件的使用方法,同时介绍了路由事件与传统CLR事件的区别以及路由事件的优点。

时间: 2024-10-27 07:31:28

WPF自定义路由事件的相关文章

WPF自定义路由事件(二)

WPF中的路由事件 as U know,和以前Windows消息事件区别不再多讲,这篇博文中,将首先回顾下WPF内置的路由事件的用法,然后在此基础上自定义一个路由事件. 1.WPF内置路由事件 WPF中的大多数事件都是路由事件,WPF有3中路由策略: 具体不多讲,单需要注意的是WPF路由事件是沿着VIsualTree传递的.VisualTree与LogicalTree的区别在于:LogicalTree的叶子节点是构成用户界面的控件(xaml紧密相关),而VisualTree要连控件中的细微结构也

WPF:自定义路由事件的实现

路由事件通过EventManager,RegisterRoutedEvent方法注册,通过AddHandler和RemoveHandler来关联和解除关联的事件处理函数:通过RaiseEvent方法来触发事件:通过传统的CLR事件来封装后供用户使用. 如何实现自定义路由事件,可以参考MSDN官网上的文档:如何:创建自定义路由事件 下面的这个demo参考自<葵花宝典--WPF自学手册>. 1.MainWindow.xaml 1 <Window x:Class="WpfApplic

WPF的路由事件、冒泡事件、隧道事件(预览事件)

原文:WPF的路由事件.冒泡事件.隧道事件(预览事件) 本文摘要: 1:什么是路由事件: 2:中断事件路由: 3:自定义路由事件: 4:为什么需要自定义路由事件: 5:什么是冒泡事件和预览事件(隧道事件): 1:什么是路由事件 WPF中的事件为路由事件,所谓路由事件,MSDN定义如下: 功能定义:路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件. 实现定义:路由事件是一个 CLR 事件,可以由 RoutedEvent 类的实例提供支持并由 Window

迟到的 WPF 学习 —— 路由事件

1. 理解路由事件:WPF 通过事件路由(event routing)概念增强了传统的事件执行的能力和范围,允许源自某个元素的事件由另一个元素引发,例如,事件路由允许工具栏上的一个按钮点击的事件在被代码处理之前上传到工具栏,再由工具栏上传到所属窗体 2. 定义.注册和包装路由事件:和依赖性属性类似,它由只读的静态字段表示,在一个静态构造函数中注册,并通过一个标准的 .Net 事件定义进行包装.如 Button 的 Click 事件,该事件继承自抽象的 ButtonBase 基类 public a

自定义路由事件

自定义路由事件大体上可分为三个步骤: 1.声明并注册路由事件: 2.为路由事件添加CLR事件包装: 3.创建可以激发路由事件的方法. 主要的示例代码如下: public class TimeButton : Button { /// <summary> /// 声明并注册路由事件. /// </summary> public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent(

WPF中路由事件的传播

路由事件(RoutedEvent)是WPF中新增的事件,使用起来与传统的事件差别不大, 但传播方式是完全不同的. 路由事件的传播方式 通过RoutingStrategy来定义传播的方式 public enum RoutingStrategy { Tunnel = 0, //隧道,由顶层元素向内传播,事件一般以Preview开头 Bubble = 1, //冒泡,与隧道相反,向外传播 Direct = 2, //直接,与传统的事件相似 } WPF中的路由事件用的最多的就是Tunnel和Bubble

学习WPF——了解路由事件

入门 我们先来看一个例子 前台代码: 后台代码: 点击按钮的运行效果第一个弹出窗口 第二个弹出窗口: 第三个弹出窗口: 说明 当点击按钮之后,先触发按钮的click事件,再上查找,发现stackpanel也注册了该事件,那么接着触发StackPanel的Button.Click事件,依次再触发Grid的Button.Click事件,这就是最基本的事件路由,事件路由的策略是右内向外的 如果不希望在XAML中注册路由事件,那么也可以通过编码的方式注册路由事件如下所示 如果想终止事件的向上传递,可以使

WPF 添加自定义路由事件

给button  Btn添加自定义的路由事件Backdoor; Btn.AddHandler(Button.MouseUpEvent, new RoutedEventHandler(Backdoor), true); 版权声明:本文为博主原创文章,未经博主允许不得转载.

WPF Demo18 路由事件

using System.Windows; namespace 路由事件2 { public class Student { ////声明并定义路由事件 //public static readonly RoutedEvent NameChangedEvent = // EventManager.RegisterRoutedEvent("NameChanged", // RoutingStrategy.Bubble, // typeof(RoutedEventHandler), //