【WPF学习】第十三章 理解路由事件
每个.NET开发人员都熟悉“事件”的思想——当有意义的事情发生时,由对象(如WPF元素)发送的用于通知代码的消息。WPF通过事件路由(event routing)的概念增强了.NET事件模型。事件路由允许源自某个元素的事件由另一个元素引发。例如,使用事件路由,来自工具栏按钮的单击事件可在被代码处理之前上传到工具栏,然后上传到包含工具栏的窗口。
事件路由为在最合适的位置编写紧凑的、组织良好的用于处理事件的代码提供了灵活性。要使用WPF内容模型,事件路由也是必需的,内容模型允许使用许多不同的元素构建简单元素(如按钮),并且这些元素都拥有自己独立的事件集合。
一、定义、注册和封装路由事件
WPF事件模型和WPF属性模型非常类似。与依赖项属性一样,路由事件由只读的静态字段表示,在静态构造函数中注册,并通过标准的.NET事件定义进行封装。
例如,WPF的Button类提供了大家熟悉的Click事件,该事件继承自抽象的ButtonBase基类。下面的代码说明了该事件是如何被定义和注册的:
public abstract class ButtonBase:ContentControl,...
{
public static readonly RoutedEvent ClickEvent;
static ButtonBase()
{
ButtonBase.ClickEvent=EventManager.RegisterRoutedEvent("Click",RoutingStrategy.Bubble,typeof(RoutedEventHandler),typeof(ButtonBase));
...
} public event RoutedEventHandler Click
{
add
{
base.AddHandler(ButtonBase.ClickEvent,value);
}
remove
{
base.RemoveHandler(ButtonBase.ClickEvent,value);
}
}
...
}
依赖项属性是使用DependencyProperty.Register()方法注册的,而路由事件是使用EvenetManager.RegisterRoutedEvent()方法注册的。当注册事件时,需要制定事件的名称、路由类型、定义事件处理程序语法的委托以及拥有事件的类。
通常,路由事件通过普遍的.NET事件进行封装,从而使用所有.NET语言都能访问他们。事件封装器可使用AddHandler()和RemoveHandler()方法添加和删除已注册的调用程序,这两个方法都在FrameworkElement基类中定义,并被每个WPF元素继承。
二、共享路由事件
与依赖项属性一样,可在类之间共享路由事件的定义。例如,UIElement(该类是所有普通WPF元素的起点)和ContentElement(该类是所有内容元素的起点,内容元素是可以被放入流文档中的单独内容片断)这两个基类都使用了MouseUp事件。MouseUp事件是由System.Windows.Input.Mouse类定义的。UIElement类和ContentElement类只通过Routed-Event.AddOwner()方法重用MouseUp事件:
UIElement.MouseUpEvent=Mouse.MouseUpEvent.AddOwner(typeof(UIElement));
三、引发路由事件
当然,与所有事件类似,定义类需要在一些情况下引发事件。到底在哪里发生是实现细节。然而,重要的细节是事件不是通过传统的.NET事件封装器引发的,而是使用RaiseEvent()方法引发事件,所有元素都从UIElement类基础了该方法。下面是来自ButtonBase类深层的代码:
RoutedEventArgs e=new RoutedEventArgs(ButtonBase.ClickEvent,this);
base.RaiseEvent(e);
RaiseEvent()方法负责为每个已经通过AddHandler()方法注册的调用程序引发事件。因为AddHandler()方法是公有的,所有调用程序可访问该方法——他们能够通过直接调用AddHandler()方法注册他们自己,也可以使用事件封装器。无论使用哪种方法,当调用RaiseEvent()方法时都会通知他们。
所有WPF事件都为事件签名使用熟悉的.NET约定。每个事件处理程序的第一个参数(sender参数)都提供引发该事件的对象的引用。第二个参数是EventArgs对象,该对象与其他所有可能很重要的附加细节绑定在一起。例如,MouseUp事件提供了一个MouseEventArgs对象,用于指示当事件发生时按下了哪些鼠标键:
private void img_MouseUp(object sender,MouseButtonEventArgs e)
{
}
在WPF中,如果事件不需要传递任何额外细节,可使用RoutedEventArgs类,该类包含了有关如何传递事件的一些细节。如果事件确实需要传递额外的信息,那么需要使用更特殊的继承自RoutedEventArgs的对象(如上面示例中的MouseButtonEventArgs)。因为每个WPF事件参数都继承自RoutedEventArgs类,所以每个WPF事件处理程序都可访问与事件路由相关的信息。
四、处理路由事件
正如前几章介绍,可以使用多种方法关联事件处理程序。最常用的方法是为XAML标记添加事件特性。事件特性按照想要处理的事件命名,它的值就是事件处理程序方法的名称。下面的示例使用这一语法将Image对象的MouseUp事件连接到名为img_MouseUp的事件处理程序:
<Image Source="a.jpg" Stretch="None" Name="img" MouseUp="img_MouseUp" />
通常约定以"元素名_事件名"的形式命名事件处理程序方法,但这不是必需的。如果没有为元素定义名称(可能是因为不需要在代码的任何地方与元素进行交互),可考虑使用以下形式的事件名称:
<Button Click="cmdOK_Click">OK</Button>
也可以使用代码连接事件。下面的代码和上面给出的XAML标记具有相同的效果:
img.MouseUp+=new MouseButtonEventHandler(img_MouseUp);
上面的代码创建了一个针对该事件具有正确的签名的委托对象(在该例中,是MouseButtonEventHandler委托的实例),并将该委托指向img_MouseUp()方法。然后将该委托添加到img.MouseUp事件的已注册的事件处理程序列表中。
C#还允许使用更精简的语法,隐式地创建合适的委托对象:
img.MouseUp+=img_MouseUp;
如果需要动态创建控件,并在窗口生命周期的某一时刻关联事件处理程序,代码方法是非常有用的。相比而言,在XAML中关联的事件总在窗口对象第一次实例化时就被关联到相应的事件处理程序。代码方法是XAML更简单,更精练,如果计划于非编程人员(如艺术设计人员)合作,这是非常好的。缺点是大量的样板代码会使代码文件变得杂乱五章。
上面的代码方法依赖与事件封装器,时间封装器调用UIElement.AddHandler()方法。也可以自行通过调用UIElement.AddHandler()方法直接连接事件。下面是一个示例:
img.AddHandler(Image.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));
当使用这种方法时,始终需要创建合适的委托类型(如MouseButtonEventHandler),而不能隐式地创建委托对象(这与通过属性封装器关联事件时不同)。这是因为UIElement.AddHandler()方法支持所有WPF事件,并且它不知道你想使用的委托类型。
有些开发人员更喜欢使用定义事件的类的名称,而不是引发事件的类的名称。例如下面的等效语法使得MouseUp事件在UIElement中定义的这一事实更加清晰:
img.AddHandler(UIElment.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));
如果想断开事件处理程序,那么只能使用代码。可使用-=运算符,如下所示:
img.MouseUp -=img_MouseUp;
或者使用UIElement.RemoveHandler()方法:
img.RemoveHandler(UIElment.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));
为同一事件多次连接相同的事件处理程序,在技术角度上可行的,这通常是编码错误的结果(这种情况下,事件处理程序会触发多次)。如果试图删除已经连接了两次的事件处理程序,事件仍会触发事件处理程序,但只触发一次。
【WPF学习】第十三章 理解路由事件的更多相关文章
- 整理:WPF用于绑定命令和触发路由事件的自定义控件写法
原文:整理:WPF用于绑定命令和触发路由事件的自定义控件写法 目的:自定义一个控件,当点击按钮是触发到ViewModel(业务逻辑部分)和Xaml路由事件(页面逻辑部分) 自定义控件增加IComman ...
- 在Button样式中添加EventSetter,理解路由事件
XML <Window.Resources> <Style x:Key="ButtonStyle2" TargetType="{x:Type Butto ...
- 【WPF学习】第一章 XAML介绍
XAML(Extensible Application Markup Language的简写,发音为“zammel”)是用于实例化.NET对象的标记语言.尽管XAML是一种应用于诸多不同问题领域的技术 ...
- Android的进阶学习(六)--理解View事件分发
http://www.jianshu.com/p/34cb396104a7 有些无奈,期末考试抱佛脚,还好没有挂,现在继续进阶. 好久以前就看到了View的事件分发,但是当时功底不够,源码也不敢深究, ...
- 【WPF学习】第四十一章 变换
通过使用变换(transform),许多绘图任务将更趋简单:变换是通过不加通告地切换形状或元素使用的坐标系统来改变形状或元素绘制方式的对象.在WPF中,变换由继承自System.Windows.Med ...
- WPF教程六:理解WPF中的隧道路由和冒泡路由事件
WPF中使用路由事件升级了传统应用开发中的事件,在WPF中使用路由事件能更好的处理事件相关的逻辑,我们从这篇开始整理事件的用法和什么是直接路由,什么是冒泡路由,以及什么是隧道路由. 事件最基本的用法 ...
- 迟到的 WPF 学习 —— 路由事件
1. 理解路由事件:WPF 通过事件路由(event routing)概念增强了传统的事件执行的能力和范围,允许源自某个元素的事件由另一个元素引发,例如,事件路由允许工具栏上的一个按钮点击的事件在被代 ...
- WPF自学入门(三)WPF路由事件之内置路由事件
有没有想过在.NET中已经有了事件机制,为什么在WPF中不直接使用.NET事件要加入路由事件来取代事件呢?最直观的原因就是典型的WPF应用程序使用很多元素关联和组合起来,是否还记得在WPF自学入门(一 ...
- WPF - 善用路由事件
原文:WPF - 善用路由事件 在原来的公司中,编写自定义控件是常常遇到的任务.但这些控件常常拥有一个不怎么好的特点:无论是内部还是外部都没有使用路由事件.那我们应该怎样宰自定义控件开发中使用路由事件 ...
随机推荐
- linux vmalloc 和 其友
我们展示给你的下一个内存分配函数是 vmlloc, 它在虚拟内存空间分配一块连续的内存 区. 尽管这些页在物理内存中不连续 (使用一个单独的对 alloc_page 的调用来获得每个 页), 内核看它 ...
- BIO、NIO、AIO 个人总结
BIO(blocking io) BIO即为阻塞IO,在网络编程中,它会在建立连接和等待连接的对端准备数据阶段进行阻塞.因此为了支撑高并发的用户访问,一般会为每一个socket 连接分配一个线程.但使 ...
- dotnet 如何在 Mock 模拟 Func 判断调用次数
在 dotnet 程序有很好用的 Mock 框架,可以用来模拟各种接口和抽象类,可以用来测试某个注入接口的被调用次数和被调用时传入参数.本文告诉大家如何在 Mock 里面模拟一个 Func 同时模拟返 ...
- 2018-10-18-WPF-跨线程-UI-的方法
title author date CreateTime categories WPF 跨线程 UI 的方法 lindexi 2018-10-18 10:25:28 +0800 2018-10-18 ...
- .net core允许跨域
// 设置允许所有来源跨域 app.UseCors(options => { options.AllowAnyHeader(); options.AllowAnyMethod(); option ...
- 使用SuperWebSocket实现Web消息推送
在大部分Web系统中,我们可能遇到需要向客户端推送消息的需求.SuperWebSocket第三方库能让我们轻松的完成任务.SuperWebSocket第三方库可以从网上下载,不过通过Visual St ...
- 十二、格式化I/O
1.fprintf 表头文件 #include<stdio.h> 定义函数 int fprintf(FILE * stream, const char * format,.......); ...
- HBase的TTL介绍
1. 定义 TTL(Time to Live) 用于限定数据的超时时间. 2.原理 以Column Family的TTL为例介绍, hbase(main):001:0> desc 'wxy:te ...
- 我们基于kaldi开发的嵌入式语音识别系统升级成深度学习啦
先前的文章<三个小白是如何在三个月内搭一个基于kaldi的嵌入式在线语音识别系统的>说我们花了不到三个月的时间搭了一个基于kaldi的嵌入式语音识别系统,不过它是基于传统的GMM-HMM的 ...
- python知识点总结02(不定时更新)
请用至少两种方式实现m与n值交换m=10,n=5 # 方式一 temp = 0 m = 10 n = 5 print(f'方式一交换前,m:{},n:{}') temp = m m = n n = t ...