总结WPF中的路由事件,我将学到的内容分为四部分来逐渐掌握

第一部分:wpf中内置的路由事件

以Button的Click事件来说明内置路由事件的使用

XAML代码:

<Window x:Class="WpfApp1112.Window3"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window3" Height="" Width="">
<Grid x:Name="gridRoot" Background="Lime" >
<Grid x:Name="gridA" Margin="" Background="Blue">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Canvas x:Name="canvasLeft" Grid.Column="" Background="Red" Margin="">
<Button x:Name="btnLeft" Content="Left" Width="" Height="" Margin=""/>
</Canvas>
<Canvas x:Name="canvasRight" Grid.Column="" Background="Yellow" Margin="">
<Button x:Name="btnRight" Content="Right" Width="" Height="" Margin=""/>
</Canvas>
</Grid>
</Grid>
</Window>

下面让gridRoot控件侦听Button.Click事件,用AddHandler方法把侦听的事件和事件处理器关联起来。

        public Window3()
{
InitializeComponent();
this.gridRoot.AddHandler(Button.ClickEvent, new RoutedEventHandler(this.ButtonClicked));
}
private void ButtonClicked(object sender,RoutedEventArgs e)
{
MessageBox.Show((e.OriginalSource as FrameworkElement).Name);
}

因为路由事件是从内部一层一层传递出来到最外层的gridRoot,并且由gridRoot元素将事件消息交给ButtonClicked方法来处理,所以传入ButtonClicked方法的sender参数是gridRoot而不是被单击的Button,所以用e.OriginalSource来查看事件的源头,但必须转换成正确的类型。运行结果如下:

以上是在后台为元素添加路由事件,也可以在XAML里完成,如下:

......
<Grid x:Name="gridRoot" Background="Lime" Button.Click="ButtonClicked">
<Grid x:Name="gridA" Margin="" Background="Blue">
......

第二部分:自定义路由事件

自定义路由事件分为三个步骤:

(1)声明并注册路由事件

(2)为路由事件添加CLR事件包装

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

下面来创建一个路由事件,这个事件的用途是报告事件发生的时间。

后台代码如下:

    /// <summary>
/// Window4.xaml 的交互逻辑
/// </summary>
public partial class Window4 : Window
{
public Window4()
{
InitializeComponent();
}
//路由事件处理器
private void ReportTimeHandler(object sender, ReportTimeEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
string timeStr = e.ClickTime.ToLongTimeString();
string content = string.Format("{0}到达{1}", timeStr, element.Name);
this.listBox.Items.Add(content); //if (element.Name == "grid_2")
//{
// e.Handled = true; //stop
//}
}
} /// <summary>
/// 为了让事件消息能携带按钮被单击时的时间,我们创建一个RoutedEventArgs类的派送类,为其添加ClickTime属性;
/// </summary>
public class ReportTimeEventArgs : RoutedEventArgs
{
public ReportTimeEventArgs(RoutedEvent routedEvent, object source)
: base(routedEvent, source)
{
}
public DateTime ClickTime
{
get;
set;
}
} /// <summary>
/// 创建一派生自Button的TimeButton类型
/// </summary>
public class TimeButton : Button
{
//声明和注册路由事件
public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent
("ReportTime", RoutingStrategy.Bubble, typeof(EventHandler<ReportTimeEventArgs>), typeof(TimeButton)); //CLR事件包装器
public event RoutedEventHandler ReportTime
{
add { this.AddHandler(ReportTimeEvent, value); }
remove { this.RemoveHandler(ReportTimeEvent, value); }
} //激发路由事件,借用Click事件的激发方法
protected override void OnClick()
{
base.OnClick(); ReportTimeEventArgs args = new ReportTimeEventArgs(ReportTimeEvent, this);
args.ClickTime = DateTime.Now;
this.RaiseEvent(args);
}
}

XAML代码如下:

<Window x:Class="WpfApp1112.Window4"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1112"
Title="Window4" Height="" Width="" Name="window4"
local:TimeButton.ReportTime="ReportTimeHandler">
<Grid x:Name="grid_1" local:TimeButton.ReportTime="ReportTimeHandler">
<Grid x:Name="grid_2" local:TimeButton.ReportTime="ReportTimeHandler">
<Grid x:Name="grid_3" local:TimeButton.ReportTime="ReportTimeHandler">
<StackPanel x:Name="stackPanel_1"
local:TimeButton.ReportTime="ReportTimeHandler">
<ListBox x:Name="listBox"/>
<local:TimeButton x:Name="timeButton" Width="" Height=""
Content="报时" local:TimeButton.ReportTime="ReportTimeHandler"/>
</StackPanel>
</Grid>
</Grid>
</Grid>
</Window>

在前台我们为Window、grid_1、grid_2、grid_3、stackPanel_1、timeButton元素都注册了ReportTime事件。

运行结果如下:

下面来说明下以上代码中的几个重点:

1、注册代码的四个参数:

public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent
("ReportTime", RoutingStrategy.Bubble, typeof(EventHandler<ReportTimeEventArgs>), typeof(TimeButton));

第一个参数:是string类型,是路由事件的名称,应与CLR事件包装器的名称和路由事件的变量名称前缀一样,即路由事件ReportTimeEvent的前缀ReportTime一样。

第二个参数:路由事件的策略,有三种:

Bubble(冒泡式)------路由事件由事件的激发者出发向它的上级容器一层一层路由,直至最外层容器(window或page)。

Tunnel(隧道式)------与Bubble相反,从外层向内层一层一层路由。

Direct(直达式)-------直接将事件消息送达事件处理器。

ReportTimeEvent策略为Tunnel时,运行结果为:

ReportTimeEvent策略为Direct时,运行结果为:

如果我们想让一个路由事件到某个结点不想让它再继续传递,我们只需要把路由事件携带的事件参数必须是RoutedEventArgs类或其派生类的实例,RoutedEventArgs类具有一个bool类型属性Handled,一旦被设置为true,就表示路由事件“已经被处理”了,路由事件就不会再往下传递了。修改代码如下:

        //路由事件处理器
private void ReportTimeHandler(object sender, ReportTimeEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
string timeStr = e.ClickTime.ToLongTimeString();
string content = string.Format("{0}到达{1}", timeStr, element.Name);
this.listBox.Items.Add(content); if (element.Name == "grid_2")
{
e.Handled = true; //stop
}
}

路由策略不管是Bubble或是Tunnel,都是到达元素grid_2时就结束了,运行结果如下:

第三部分:RoutedEventArgs的Source和OriginalSource

简单的总结就是:Source表示的是LogicalTree上的消息源头,而OriginalSource则表示VisualTree上的源头。

第四部分:附加事件

路由事件的宿主拥有可视化实体的界面元素,比如:Button、Slider、TextBox.......

附加事件则不具备显示在用户界面上的能力。比如:

Binding类:SourceUpdated事件、TargetUpdated事件。

Mouse类:MouseEnter事件、MouseLeave事件、MouseDown事件、MouseUp事件等。

Keyboard类:KeyDown事件、KeyUp事件等。

下面用示例来说明,设计一个名为Student的类,如果Student实例的Name属性值发生变化就激发一个路由事件,用界面元素来捕捉这个事件。

后台代码如下:

    /// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent(); Student.AddNameChangedHandler(this.gridMain, new RoutedEventHandler(this.StudentNameChangedHandler));
}
private void StudentNameChangedHandler(object sender, RoutedEventArgs e)
{
MessageBox.Show((e.OriginalSource as Student).Id.ToString());
} /// <summary>
/// Click事件处理器
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn1_Click(object sender, RoutedEventArgs e)
{
Student stu = new Student() { Id = , Name = "Tim" };
stu.Name = "Tom"; //准备事件消息并发送路由事件
RoutedEventArgs arg = new RoutedEventArgs(Student.NameChangedEvent, stu);
this.btn1.RaiseEvent(arg);
}
} public class Student
{
//声明并注册事件
public static readonly RoutedEvent NameChangedEvent = EventManager.RegisterRoutedEvent
("NameChanged",RoutingStrategy.Bubble,typeof(RoutedEventHandler),typeof(Student)); //为界面元素添加路由事件侦听
public static void AddNameChangedHandler(DependencyObject d, RoutedEventHandler h)
{
UIElement e = d as UIElement;
if (e != null)
{
e.AddHandler(Student.NameChangedEvent, h);
}
}
//移除侦听
public static void RemoveNameChangedHandler(DependencyObject d, RoutedEventHandler h)
{
UIElement e = d as UIElement;
if (e != null)
{
e.RemoveHandler(Student.NameChangedEvent, h);
}
} public int Id { get; set; }
public string Name{get;set ;}
}

XAML代码如下:

<Window x:Class="WpfApp1120.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="" Width="">
<Grid x:Name="gridMain">
<Button x:Name="btn1" Content="OK" Width="" Height="" Click="btn1_Click"/>
</Grid>
</Window>

注意:Student类非派生自UIElement,因此不具备AddHandler和RemoveHandler这两个方法,所以不能使用CLR属性作为包装器(因为CLR属性包装器的add和remove分支分别调用当前对象的AddHandler 和 RemoveHandler)。所以微软规定:

1、为目标UI元素添加附近事件侦听器的包装器是一个名为 Add(事件名称)Handler的Public static 方法。

2、解除UI元素对附加事件侦听的包装器是名为Removea(事件名称)Handler 的Public static 方法。

如上面实例中的 AddNameChangedHandler 和 RemoveNameChangedHandler 方法。

【WPF】路由事件的更多相关文章

  1. WPF自学入门(三)WPF路由事件之内置路由事件

    有没有想过在.NET中已经有了事件机制,为什么在WPF中不直接使用.NET事件要加入路由事件来取代事件呢?最直观的原因就是典型的WPF应用程序使用很多元素关联和组合起来,是否还记得在WPF自学入门(一 ...

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

    一.什么是路由事件 路由事件是一种可以针对元素树中的多个侦听器而不是仅仅针对引发该事件的对象调用处理程序的事件.路由事件是一个CLR事件. 路由事件与一般事件的区别在于:路由事件是一种用于元素树的事件 ...

  3. WPF 路由事件 Event Routing

    原文:WPF 路由事件 Event Routing 1.路由事件介绍 之前介绍了WPF的新的依赖属性系统,本篇将介绍更高级的路由事件,替换了之前的.net普通事件.相比.net的事件,路由事件具有更强 ...

  4. WPF 路由事件总结

    1.什么是路由事件 已下为MSDN中的定义 功能定义:路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件. 实现定义:路由事件是一个 CLR 事件,可以由 R ...

  5. .NET: WPF 路由事件

    (一)使用WPF内置路由事件 xaml: <Window x:Class="WpfApplication1.MainWindow" xmlns="http://sc ...

  6. WPF自学入门(四)WPF路由事件之自定义路由事件

    在上一遍博文中写到了内置路由事件,其实除了内置的路由事件,我们也可以进行自定义路由事件.接下来我们一起来看一下WPF中的自定义路由事件怎么进行创建吧. 创建自定义路由事件分为3个步骤: 1.声明并注册 ...

  7. WPF路由事件三:自定义路由事件

    与依赖项属性类似,WPF也为路由事件提供了WPF事件系统这一组成.为一个类型添加一个路由事件的方式与为类型添加依赖项属性的方法类似,添加一个自定义路由事件的步骤: 一.声明路由事件变量并注册:定义只读 ...

  8. WPF路由事件

    ​    这节讲一下WPF中的路由事件(Routed Event). [什么是事件] 在了解路由事件前,我们应先来了解一下什么是事件(Event). 在Windows系统中,像鼠标单击,双击,移动这样 ...

  9. WPF路由事件学习(一)

    路由事件与一般事件的区别在于:路由事件是一种用于元素树的事件,当路由事件触发后,它可以向上或向下遍历可视树和逻辑树,他用一种简单而持久的方式在每个元素上触发,而不需要任何定制的代码(如果用传统的方式实 ...

  10. WPF 路由事件

    最近想封装一个关于手势的控件,但是由其他的控件覆盖之后发现不能触发,据说是有一些事件在定义的时候就处理过e.Handle了. 定义的时候就处理了,就是为了控件能够正常的工作,别如Button.Mous ...

随机推荐

  1. opencv里面CV_32FC1家族

    因为总是接触过这样一些#define里面的东西但是总也不知道是干什么用的.而且每看一次梦比一次. 对于这些东西到底是什么的简写根本就不能理解. 原意是跑一下这个例程的: cvRectangle( my ...

  2. FIO工具常用参数

    name 可能被用于覆盖作业的名称. filename fio 通常基于该作业名称,线程编号,构成一个文件名称和位置.如果您不想让线程之间的共享文件在一个作业或作业.指定文件名都以覆盖默认的. loc ...

  3. svn学习总结

    安装svn subversion服务参考博客http://huihai.iteye.com/blog/1985238 一:svn版本管理的作用 a:备份程序   项目开发过程中,防止不可控因素造成的资 ...

  4. 【二分答案+贪心】UVa 1335 - Beijing Guards

    Beijing was once surrounded by four rings of city walls: the Forbidden City Wall, the Imperial City ...

  5. JavaScript高级程序设计(第三版)学习笔记1~5章

    第2章,在html中使用JavaScript Html引入外部js脚本 <script type="text/javascript" src="test.js&qu ...

  6. 预算oracle

    select * from ( )) CODE_VERSION from ( SELECT tb_cube_fc05.pk_entity pk_org,/*主体pk*/ org_orgs.code o ...

  7. 【HTML XHTML CSS基础教程(第6版)】笔记之HTML XHTML笔记(1~6章)

      第1章 网页的构造块   1.(X)HTML有三种主要的标记类型:元素,属性,值.   2.浏览器主要通过查看文件的扩展名(.htm或.html)来得知应该按照网页的方式读取文本文件.   3.H ...

  8. C# string.Format格式化时间或货币

    1.格式化货币(跟系统的环境有关,中文系统默认格式化人民币,英文系统格式化美元) string.Format("{0:C}",0.2) 结果为:¥0.20 (英文操作系统结果:$0 ...

  9. XML文件的解析方式

    XML文件4种解析方式分别是:DOM解析,SAX解析,JDOM解析,DOM4J解析.1.基础方法:DOM:与平台无关的官方的解析方式.SAX:Java平台提供的基于事件驱动的解析方式.2.扩展方法(在 ...

  10. 第27条:使用“class-continuation分类”隐藏实现细节

    Objective-C动态消息系统(参见第11条)的工作方式决定了其不可能实现真正的私有方法或私有实例变量. 匿名分类的特点: 与普通的分类不同,它必须定义在其所接续的那个类的实现文件里. 唯一能声明 ...