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

写在前面

本文一开始会给出一个使用WPF路由事件的实例,因为本文所有的表述都将基于该实例。而本文所给实例来自于《WPF自定义路由事件》一文,在《WPF自定义路由事件》一文中会对实例代码做详细说明,所以,大家在阅读本文实例代码期间若存在疑问,可以先去看看《WPF自定义路由事件》一文,看是否能从中获得你想要的解答。

本文实例

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

//************************************************************
//
// 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类的完整代码。

//************************************************************
//
// 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));
        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);
        }
    }
}

3 下面给出使用DetailReportButton 类的完整代码,包括画面代码以及画面后端代码两部分。

程序画面代码:

<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);
        }
    }
}

4 执行程序,得到下图所示效果。

图1 程序运行效果图

结果显示:单击画面按钮,触发路由事件,路由事件的消息便从事件的触发者开始向它的上级容器控件一层一层的往外传,直到最外层的容器控件为止。

你我的约定

因为下面的说明中会反复修改实例代码,现在给出约定:

每一次修改在说明完相关的知识点后都将回退到修改前的状态。

我的新闻发布会

下面将以问答的方式来总结我在使用WPF路由事件时遇到或想到的几个小问题。

Q1 :除了使用AddHandler方法外,还有什么方法可以将想监听的事件与事件的处理器关联起来?

可以在XAML代码中将监听的事件与事件的处理器关联起来。按照下面所给方法修改实例代码,同样可以使用自定义的路由事件。

(1)注释掉以下四行代码。

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));

(2)在XAML代码中将监听的事件与事件的处理器关联起来,具体操作见下图说明。

图2 在XAML中将监听的事件与事件处理器关联

(3)图2显示,此时使用的事件处理器不再是Button_Clicked1,而是Button_Clicked2,下面给出Button_Clicked2的代码。

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

将Button_Clicked2与Button_Clicked1进行对比,发现两者的本质区别就是:第二个参数的类型不相同。

Button_Clicked2的第二个参数的类型为DetailReportEventArgs,而Button_Clicked1的第二个参数的类型为RoutedEventArgs。

继续Q:造成这种区别的原因又是什么呢?

那是因为在使用AddHandler方法时,传入的事件处理器必须满足RoutedEventHandler的定义,而RoutedEventHandler的定义如下所示:

public delegate void RoutedEventHandler(object sender, RoutedEventArgs e)

很显然,RoutedEventHandler本质上就是一个委托,而与该委托能够匹配的方法必须满足:

第一个参数为object类型,第二个参数为RoutedEventArgs类型,且不具有返回值。

所以实例中使用了满足该委托要求的Button_Clicked1。

接着阐述,Button_Clicked2存在的原因。

查看自定义路由事件的定义代码:

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

从定义中可以找到路由事件要求的事件处理器的类型,为EventHandler<DetailReportEventArgs>。

同样可以查看EventHandler<DetailReportEventArgs>的定义,如下所示:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

将其中的TEventArgs替换成DetailReportEventArgs,得到以下定义代码:

public delegate void EventHandler<DetailReportEventArgs>(object sender, DetailReportEventArgs e);

看到这个定义,我想大家也就知道了Button_Clicked2存在的原因了吧。

Q2: 既然为路由事件增加了CLR事件包装,那么,我们是否可以通过“+=”与“-=”符号来操作路由事件?

当然可以。

下面将通过修改示例代码进行演示来说明这一点。

注释掉实例中一切将事件与事件处理器关联起来的代码(画面代码以及画面后端代码中均要这么做)。

在画面后端代码中增加以下代码:

this.Button_Confirm.DetailReport += Button_Clicked1;

运行程序,点击按钮,得到以下消息框:

图3 使用“+=”订阅事件后的运行效果图

事件处理器被正确执行,此时的路由事件与普通的CLR事件相同,直接将消息传递给绑定的事件处理器。

接着,再增加以下代码:

this.Button_Confirm.DetailReport -= Button_Clicked1;

再次运行程序,点击按钮,这下子,就“呵呵”了,我想你懂的。

Q3 :同一容器中的控件之间能够相互监听路由事件的消息吗?

按照下面的描述修改示例代码。

(1)在DetailReportButton按钮前增加名为Button_SameLayer的按钮。

<Grid x:Name="Grid_ThirdLayer" >
    <Button x:Name="Button_SameLayer"/>
    <local:DetailReportButton x:Name="Button_Confirm"  Width="100" Height="100" Content="Click Me" Margin="142,111,250,100" />
</Grid>

(2)使Button_SameLayer监听DetailReportButton的事件DetailReportEvent。

 this.Button_SameLayer.AddHandler(DetailReportButton.DetailReportEvent, new RoutedEventHandler(Button_Clicked1));

(3)运行程序,发现Button_SameLayer并没有监听到路由事件的到来。

由此可见,同一容器中的控件之间无法相互监听路由事件的消息。

Q4:如何让一个路由事件传递至某一个节点处不再继续传递?

在事件处理方法Button_Clicked1中,增加以下代码:

if (ele == this.Grid_SecondLayer)
{
    e.Handled = true;
}

这样一来,当消息传递至Grid_SecondLayer控件后将不会继续传递下去。

因为,“e.Handled= true”的意思是说:该路由事件已经被处理了,不需要再继续传递下去。

Q5: RoutedEventArgs的Source与OriginalSource的区别是什么?

为了阐述这个主题,需要对实例做相对较大的一次变动。

(1)新建用户控件MyUserControl,MyUserControl不完成任何有意义的工作,MyUserControl控件的XAML代码如下所示:

<UserControl x:Class="WpfRoutedEventExp.MyUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid x:Name="Grid_FirstLayerInUserControl">
        <Grid x:Name="Grid_SecondLayerInUserControl">
            <Button x:Name="Button_ThirdLayerInUserControl" Width="100" Height="100" Content="Hello,Kitty!"/>
        </Grid>
    </Grid>
</UserControl>

代码对应的设计视图如下图所示:

图4 自定义控件的设计图

(2)在实例代码中使用我们自定义的用户控件,并为其指定名称为myUserControl。

<Grid x:Name="Grid_ThirdLayer" >
     <local:DetailReportButton x:Name="Button_Confirm" Width="100" Height="100" Content="Click Me" Margin="142,111,250,100" />
     <local:MyUserControl x:Name="myUserControl" Width="100" Height="100" HorizontalAlignment="Left" Margin="285,110,0,100"/>  </Grid>

自定义控件myUserControl位于容器Grid_ThirdLayer之中,Button_Confirm按钮之后。

图5 使用自定义的控件

(2)去掉所有的事件监听。

(3)使Grid_FirstLayer监听Button的Click事件。

this.Grid_FirstLayer.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Clicked1));

(4)修改事件处理器方法Button_Clicked1,在方法中输出事件的Source与OriginalSource属性值。

private void Button_Clicked1(object sender, RoutedEventArgs e)
{
   MessageBox.Show(e.Source + ";" + e.OriginalSource );
}

(5)点击“Hello,Kitty!”按钮(“Hello,Kitty!”按钮位于自定义控件myUserControl中),得到以下结果:

图6 Source与OriginalSource的区别

从结果可以看到:e.Source的值为MyUserControl(即自定义控件),而e.OriginalSource的值为Button(即“Hello,Kitty!”按钮)。

Source与OriginalSource均为事件源。只不过Source表示的是LogicalTree上的消息源头,而OriginalSource表示的是VisualTree上的源头。

Button.Click路由事件是从自定义控件内部名为Button_ThirdLayerInUserControl的Button控件发出的,在主窗口中,myUserControl是LogicalTree的末端节点,所以e.Source就是MyUserControl;而窗体的VisualTree则包含了MyUserControl的内部结构,所以e.OriginalSource将返回Button_ThirdLayerInUserControl按钮。

写在后面

本文未完,以后待续...

时间: 2024-10-07 16:39:07

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

WPF路由事件二:路由事件的三种策略

一.什么是路由事件 路由事件是一种可以针对元素树中的多个侦听器而不是仅仅针对引发该事件的对象调用处理程序的事件.路由事件是一个CLR事件. 路由事件与一般事件的区别在于:路由事件是一种用于元素树的事件,当路由事件触发后,它可以向上或向下遍历可视树和逻辑树,他用一种简单而持久的方式在每个元素上触发,而不需要任何定制的代码(如果用传统的方式实现一个操作,执行整个事件的调用则需要执行代码将事件串联起来). 路由事件的路由策略: 所谓的路由策略就是指:路由事件实现遍历元素的方式. 路由事件一般使用以下三

WPF路由事件学习转(二)

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

Ubuntu14.04安装mysql及过程中遇到的一点小问题

Ubuntu14.04出来有段时间了,前几天刚升级到这个版本,新鲜了一把.不过安装mysql时遇到了一点小问题,记录一下以备后用. 预备操作: 网上安装mysql的方法不少,大体有这么三种. 1. 从网上安装 sudo apt-get install mysql-server.装完已经自动配置好环境变量,可以直接使用mysql的命令. 2. 安装离线包,以mysql-5.0.45-linux-i686-icc-glibc23.tar.gz为例. 3. 二进制包安装:安装完成已经自动配置好环境变量

关于Linq使用过程中遇见的一些小问题的总结

1 使用First()/FirstOrDefault().Last()/LastOrDefault()方法返回序列中的第一个或者最后一个元素时,应该确保序列已经被正确排序. int[] numbers = { 3, 1, 23, 10, 5, 12, 7, 2, 4}; int first = numbers.First(); //输出3 int firstOrdered = numbers.OrderBy(it => it).First();//输出1 从示例代码可以看出,对未正确排序的序列使

WPF 路由事件

每每谈到WPF的路由事件,我总是比较模糊的,因为我一般很少用,因为一般是用Binding来满足数据驱动界面的要求,要么就是通过路由命令来执行我想要做的方法,路由事件确实用得少,那么路由事件跟一般的事件的区别在哪里呢?如何使用呢?下面讲揭开其神秘的面纱: 首先说一说事件的历史,在windows操作系统上运行程序,都是消息驱动的,早期Windows API开发和MFC开发的时候可以直接看到各种消息,并且定义消息,到了VB和COM的时代,消息被封装成了事件,到了.NET时代也是事件,但是是直接式的,但

理解WPF路由事件

(一)什么时路由事件功能定义:路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件.实现定义:路由事件是一个 CLR 事件,可以由 RoutedEvent 类的实例提供支持并由 Windows Presentation Foundation (WPF) 事件系统来处理(二)路由事件的划分(1)冒泡:针对事件源调用事件处理程序.路由事件随后会路由到后续的父元素,直到到达元素树的根.(2)隧道:最初将在元素树的根处调用事件处理程序.随后,路由事件将朝着路由事件

网络-数据包在路由转发过程中MAC地址和IP地址,变与不变

关于MAC地址和IP地址在传输过程中变与不变的问题: 结论:MAC地址在同一个广播域传输过程中是不变的,在跨越广播域的时候会发生改变的:而IP地址在传输过程中是不会改变的(除NAT的时候),总结为 路由转发MAC不变,IP变. 我们知道的几个概念: 首先我们要知道,MAC地址是用于同意物理或逻辑第2层网络上的设备间进行通信的: 而第三层地址(IP地址)是可以在多个网络设备之间通信的. 下面我们来分析一下: MAC地址是在同一个广播域有效的,那么去了另外一个广播域(网段)MAC地址肯定要改变的:

WPF路由事件一:逻辑树和可视树

一.什么是逻辑树 逻辑树就是描述WPF界面元素的实际构成,它是由程序在XAML中所有的UI元素组成.最显著的特点就是由布局控件.或者其他常用的控件组成. 1 <Window x:Class="WpfRouteEvent.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.c

WPF路由事件:逻辑树和可视树

一.什么是逻辑树 逻辑树就是描述WPF界面元素的实际构成,它是由程序在XAML中所有的UI元素组成.最显著的特点就是由布局控件.或者其他常用的控件组成. 1 <Window x:Class="WpfRouteEvent.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.c