《深入浅出WPF》笔记——事件篇
如果对事件一点都不了解或者是模棱两可的话,建议先去看张子阳的委托与事件的文章(比较长,或许看完了,也忘记看这一篇了,没事,我会原谅你的)http://www.cnblogs.com/JimmyZhang/archive/2007/09/23/903360.html,废话不多说,开始进入正题。本记录不是记录传统的事件(CLR事件),而是记录WPF中常用的事件——路由事件,由于路由事件“传播”的时间是沿着可视树传播的,所以在记录开始之前还是了解一下逻辑树和可视树。
一、逻辑树和可视树
在WPF中有两种树:逻辑树(Logical Tree)和可视树(Visual Tree),XAML是表达WPF的一棵树。逻辑树完全是由布局组件和控件构成。如果我们把逻辑树延伸至Template组件级别,我们就得到了可视树,所以可视树把树分的更细致。由于本记录重在记录事件,所以不做过多表述逻辑树和可视树的内容。关于逻辑树和可视树的区别可以参考http://www.cnblogs.com/Clingingboy/archive/2010/08/06/1793923.html。
二、路由事件
2.1、小记事件
如果看完了委托与事件的文章,相信会对事件有更进一步的认识了,但还是要把一些基础的地方再记录一下。一个事件要有下面几个要素,才会变的有意义:
- 事件的拥有者(sender)——即消息的发送者。
- 事件发送的消息(EventAgs)
- 事件的响应者——消息的接收者(对象)。
- 响应者的处理器——消息的接受者要对消息作出处理的方法(方法名)。
- 响应者对发送者事件的订阅
2.2 初试路由事件
我们建立一个winform项目,然后在窗体上添加一个按钮,双击添加一个处理器,会发现private void btn_Click(object sender, EventArgs e)处理器要处理消息是EventArgs类型的,这里对应的是传统的事件(我们叫它CLR事件)。同样我们建立一个WPF项目,然后添加一个按钮,双击添加一个处理器,会发现private void Button_Click(object sender, RoutedEventArgs e)处理器要处理的消息是RoutedEventArgs类型的,这里对应的是路由事件。两者有什么区别呢。让我们先看看CLR事件的弊端,就如(this.btn.Click += new System.EventHandler(this.btn_Click);)每一个消息都是从发送到响应的一个过程,当一个处理器要用多次,必须建立显式的点对点订阅关系(窗体对按钮事件的订阅,如果是再有一个按钮的话,就要再来一次订阅);还有一个弊端是:事件的宿主必须能够直接访问事件的响应者,不然无法建立订阅关系(如有两个组件,点击组件一的按钮,想让组件二响应事件,那么就让组件二向组件一的按钮暴露一个可以访问的事件,这样如果再多几个嵌套,会出现事件链,有暴露如果暴露不当就存在着威胁)。路由事件除了能很好的解决上面的问题,还有一个是路由事件在有路的情况下,能很好的按照规定的方式传播事件,因为XAML的树状结构,构成了一条条的道路,所以在WPF中,引入了路由事件。举个例子:如果窗体要以相同的方式处理两个按钮的事件,我们就可以用一句代码就搞定了,this.AddHandler(Button.ClickEvent, new RoutedEventHandler(this.ButtonClicked));这样就减少代码量。下面通过一个例子初试一下路由事件。 给出XAML代码:
<Window x:Class="Chapter_06.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="GridRoot" Background="Lime">
<Grid x:Name="gridA" Margin="10" Background="Blue">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Canvas x:Name="canvasLeft" Grid.Column="0" Background="Red" Margin="10">
<Button x:Name="buttonLeft" Content="left" Width="40" Height="100" Margin="10"/>
</Canvas>
<Canvas x:Name="canvasRight" Grid.Column="1" Background="Yellow" Margin="10">
<Button x:Name="buttonRight" Content="right" Width="40" Height="100" Margin="10" />
</Canvas>
</Grid>
</Grid>
</Window>
我们点击按钮时,无论是buttonLeft还是buttonRight单击都能显示按钮的名称。两个按钮到顶部的window有唯一条路,左边的按钮对应的路:buttonLeft->canvasLeft->gridA->GridRoot->Window,右边按钮对应的路:buttonRight->canvasRight->gridA->GridRoot->Window。如果GridRoot订阅两个处理器,那么处理器应该是相同的。后台代码为:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes; namespace Chapter_06
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.GridRoot.AddHandler(Button.ClickEvent,new RoutedEventHandler(this.ButtonClicked));
}
private void ButtonClicked(object sender, RoutedEventArgs e)
{
MessageBox.Show((e.OriginalSource as FrameworkElement).Name);
}
}
}
下面先解释一下路由事件是怎么沿着可视树来传播的,当Button被点击,Button就开始发送消息了,可视树上的元素如果订阅了Button的点击事件,那么才会根据消息来作出相应的反应,如果没有订阅的话,就无视它发出的消息,当然我们还可以控制它的消息的传播方式,是从树根到树叶传播,还是树叶向树根传播以及是直接到达目的传播,不仅如此,还能控制消息传到某个元素时,停止传播。具体的会在后面记录到。其次是this.GridRoot.AddHandler(Button.ClickEvent,new RoutedEventHandler(this.ButtonClicked));订阅事件时,第一个参数是路由事件类型,在这里用的是Button的ClickEvent,就像依赖属性一样,类名加上依赖属性,这里是类名加上路由事件。另外一个是e.OriginalSource与e.Source的区别。由于消息每传一站,都要把消息交个一个控件(此控件成为了消息的发送地点),e.Source为逻辑树上的源头,要想获取原始发消息的控件(可视树的源头)要用e.OriginalSource。最后说明一下,怎么在XAML中添加订阅事件。直接用<Grid x:Name="gridA" Margin="10" Background="Blue" Button.Click="ButtonClicked">在.net平台上不能智能提示Button,因为Click是继承与ButtonBase的事件,XAML只认识含有Click事件的元素,但是要大胆的写下去才能成功。运行上面代码,点击左边按钮,效果如图1:
默认的路由消息里面属性有四个,如图2,可以自行转到定义看一下其属性代表的意义。
2.3自定义路由事件
通过上面的小试路由事件,应该对路由事件有些了解了,下面就记录一下如何自定义一个路由事件。大致分为三个步骤:1.声明并注册路由事件,2.为路由事件添加CLR事件包装,3.创建可以激发路由事件的方法。回忆一下依赖属性,前两个步骤应该和路由事件很相似吧。下面将三个步骤分开来说明:
第一步:声明并注册路由事件
//***Event为路由事件名,类型为路由事件类型
//注册事件用的是EventManager.RegisterRoutedEvent
//第一个参数为事件名(下面的***都为同一个单词)
//第二个参数为事件传播的策略,有三种策略:Bubble(冒泡式),Tunnel(隧道式),Direct(直达式)分别对应上面的三种青色字体的三种方式
//第三个参数用于指定事件处理器的类型,该类型必须为委托类型,并且不能为 null。
//第四个参数为路由事件的宿主
public static readonly RoutedEvent ***Event = EventManager.RegisterRoutedEvent("***", RoutingStrategy.Bubble,
typeof(***RouteEventHandler), typeof(ClassName)); 第二步:为路由事件添加CLR事件包装
/*包装事件
*这里与传统的数据差别是把+=和-=换成了AddHandler和RemovedHandler
*/
public event RoutedEventHandler ***
{
add { this.AddHandler(***Event, value); }
remove { this.RemoveHandler(***Event, value); }
}
第三步:创建可以激发路由事件的方法
/*对于控件的事件,一般是重写宿主事件对应的方法(如Button的click事件和OnClick()方法相对应):新建消息,并把消息与路由事件相关联,
*通过调用元素的RaiseEvent方法把时间传送出去(这里与包装器的CRL事件毫不相干),在CLR事件是用Invoke方法,下面以按钮为例
*/ protected override void OnClick()
{
base.OnClick();
***EventArgs args = new ***EventArgs(***Event, this);
this.RaiseEvent(args);
}
下面我们就来实现一个简单的自定义路由功能,当路由飘过一个控件的时间,显示通过该控件的时间。 上面介绍的差不多了,所以就直接上代码,有需要解释的话,再一个个解释。
下面是XAML代码:
<Window x:Class="DefineEvent.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DefineEvent"
Title="Routed Event" x:Name="window_1" Height="350" Width="525" 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="200" Height="80" Content="显示到达某个位置的时间" ReportTime="ReportTimeHandler"/>
</StackPanel>
</Grid>
</Grid>
</Grid>
</Window>
下面是CS代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel; namespace DefineEvent
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
delegate void ReportTimeRouteEventHandler(object sender, ReportTimeEventArgs e);
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ReportTimeHandler(object sender, ReportTimeEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
string timeStr = e.ClickTime.ToString("yyyyMMddHHmmss");
string content = string.Format("{0}到达{1}", timeStr, element.Name);
this.listBox.Items.Add(content);
}
}
//创建消息类型,在此可以附加自己想要的信息
public class ReportTimeEventArgs : RoutedEventArgs
{
public ReportTimeEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) { }
public DateTime ClickTime { get; set; }
}
public class TimeButton : Button
{
//1、为元素声明并注册事件
public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent("ReportTime", RoutingStrategy.Bubble,
typeof(ReportTimeRouteEventHandler), typeof(TimeButton)); //2、包装事件
public event RoutedEventHandler ReportTime
{
add { this.AddHandler(ReportTimeEvent,value); }
remove { this.RemoveHandler(ReportTimeEvent,value); }
}
//3、创建激发事件的方法
protected override void OnClick()
{
base.OnClick();
ReportTimeEventArgs args = new ReportTimeEventArgs(ReportTimeEvent,this);
args.ClickTime = DateTime.Now;
this.RaiseEvent(args);
}
}
}
运行单击按钮的效果为图3:
注意下面的一行代码,在声明和定义路由事件时,第三个参数,委托的类型和处理器方法的的参数一定要相同,不然会报错,可以把下面一句中的ReportTimeRouteEventHandler换成RoutedEventHandler试试,会出现:无法从文本“ReportTimeHandler”创建“ReportTime”。”,行号为“5”,行位置为“30”,的问题,这里主要的原因就是委托的参数和RoutedEventHandler的参数不一致,虽然都是(sender, e);但是e的类型已经发生了变化,成为了ReportTimeEventArgs类型的。所以在使用之前,声明一个委托就可以了。还有个方法是使用EventHandler<ReportTimeEventArgs>替换ReportTimeRouteEventHandler,其实二者的用法差不多,只是不同的写法,但是是我感觉第一种写法会更好理解。具体关于EventHandler<ReportTimeEventArgs>的含义请参考http://book.51cto.com/art/200811/98553.htm。我们同样可以使用让第二个参数改变成另外两种类型的看看测试结果。public
static
readonly
RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent(
"ReportTime"
, RoutingStrategy.Tunnel,
typeof
(ReportTimeRouteEventHandler),
typeof
(TimeButton));
如果我们希望当事件传递到grid_2上面就停止了,我们可以这样做:在ReportTimeHandler函数中添加代码。
if (element.Name == "grid_2" ) e.Handled = true ; |
三、总结
本篇记录的内容虽然不多,但是感觉记录的时间特别费力,主要是因为对事件的几个组成部分还不是非常熟练,而且在理解路由事件时,还要先理解逻辑树与可视树。最终还是把这一章看完了,但这个只是开始。
文章主要记录了路由事件的在可视树上的传播以及自定义路由事件的实现。如果在文章有不同的见解或建议,欢迎交流! 下一篇:《深入浅出WPF》笔记——命令篇
《深入浅出WPF》笔记——事件篇的更多相关文章
- C# Note1:深入浅出WPF-MVVM篇
一.资源说明 (1)配套视频:深入浅出WPF 讲的不错! 待更!
- 深入浅出WPF笔记
数据层(Database,Oracle等) 业务逻辑层(Service,Data Access Layer,WCF) 表示层(WPF,Win Form,ASP.net,Silverlight) [WP ...
- 深入浅出WPF——附加事件(Attached Event)
3.3 事件也附加——深入浅出附加事件 WPF事件系统中还有一种事件被称为附加事件(Attached Event),简言之,它就是路由事件.“那为什么还要起个新名字呢?”你可能会问. “身无彩凤双飞翼 ...
- 《深入浅出WPF》笔记——资源篇
原文:<深入浅出WPF>笔记--资源篇 前面的记录有的地方已经用到了资源,本文就来详细的记录一下WPF中的资源.我们平时的“资源”一词是指“资财之源”,是创造人类社会财富的源泉.在计算机程 ...
- 《深入浅出WPF》笔记——模板篇
原文:<深入浅出WPF>笔记--模板篇 我们通常说的模板是用来参照的,同样在WPF中,模板是用来作为制作控件的参照. 一.认识模板 1.1WPF菜鸟看模板 前面的记录有提过,控件主要是算法 ...
- 《深入浅出WPF》笔记——绘画与动画
<深入浅出WPF>笔记——绘画与动画 本篇将记录一下如何在WPF中绘画和设计动画,这方面一直都不是VS的强项,然而它有一套利器Blend:这方面也不是我的优势,幸好我有博客园,能记录一 ...
- 《深入浅出WPF》 学习笔记
<深入浅出WPF> 序言 1. 什么是WPF 2. 为什么要学习WPF 第一章 XAML概览 1. XAML是什么? 2. XAML有哪些优点 第二章 从零起步认识XAML 1. 新 ...
- 《深入浅出WPF》读书笔记
依赖属性: 节省实例对内存的开销: 属性值可以通过Binding依赖到其他对象上. WPF中,依赖对象的概念被DependencyObject类实现,依赖属性被DependencyProperty类实 ...
- 【【分享】深入浅出WPF全系列教程及源码
】
因为原书作者的一再要求,在此声明,本书中的部分内容引用了原书名为<深入浅出WPF>的部分内容,假设博文不能满足你现有的学习须要,能够购买正版图书! 本人10月份提出离职,可是交接非常慢,预 ...
随机推荐
- CocoaPods安装小步骤
CocoaPod的安装和使用步骤: 1.开启 terminal终端 2.移除现有 Ruby 默认源 $ gem sources --remove https://rubygems.org/ 3.使用新 ...
- 增强学习--Sarsa算法
Sarsa算法 实例代码 import numpy as np import random from collections import defaultdict from environment i ...
- scrapy-splash抓取动态数据例子九
一.介绍 本例子用scrapy-splash抓取众视网网站给定关键字抓取咨询信息. 给定关键字:个性化:融合:电视 抓取信息内如下: 1.资讯标题 2.资讯链接 3.资讯时间 4.资讯来源 二.网站信 ...
- Golang 内存热力图
https://cizixs.com/2017/09/11/profiling-golang-program/
- Python 转义html中以"&#"开头的字符
from HTMLParser import HTMLParser print HTMLParser().unescape('中国')
- UNIX管道符
在Unxi操作系统中,标准输入和标准输出是外壳程序中可以单独使用的两个独立流.但是有时候系统工程师需要让某些特定的输入源不起作用.当系统工程师在开发一些实用的脚本程序的过程中,经常需要用到. 一. ...
- java常用英语单词
abstract (关键字) 抽象 ['.bstr.kt] access vt.访问,存取 ['.kses]'(n.入口,使用权) algorithm n.算法 ['.lg.riem] annotat ...
- Google Chrome 浏览器的检索语言设置
解决为何从一开始安装Google Chrome 浏览器的时候,使用Google 搜索,结果都是日文的问题. 藏的比较隐蔽,没法在“设置”那里修改. 1.问题:搜索内容均是日文: 2.注意到右边有一个“ ...
- svn命令行版本回滚
下面以版本号2011回滚到2010为例,在命令行输入: svn merge --dry-run -r 2011:2010 http://my.repository.com/my/project/tru ...
- Azure Storage Blob Go SDK示例
简介 前面一篇博客介绍了关于Azure ManagerAPI Go SDK的使用,本篇继续介绍使用Blob Go SDK 操作中国区Azure Blob. SDK下载: go get github.c ...