WPF_05_路由事件
路由事件
WPF用更高级的路由事件替换普通的.NET事件。路由事件具有更强传播能力,可在元素树中向上冒泡和向下隧道传播,并沿着传播路径被事件处理程序处理。与依赖属性一样,路由事件由只读的静态字段表示,在静态构造函数中注册,并通过标准的.NET事件定义进行封装。
public abstract class ButtonBase : ContentControl
{
// 定义
public static readonly RoutedEvent ClickEvent;
// 注册
static ButtonBase()
{
// 事件名称 路由类型 定义事件处理程序语法的委托 拥有事件的类
ButtonBase.ClickEvent = EventManager.RegisterRoutedEvent("Click",RoutingStategy.Bubble,typeof(RoutedEventHandler),typeof(ButtonBase));
}
// 传统包装
public event RoutedEventHandler Click
{
add
{
base.AddHandler(ButtonBase.ClickEvent,value);
}
remove
{
base.RemoveHandler(ButtonBase.ClickEvent,value);
}
}
}
共享路由事件
与依赖属性一样,可以在类之间共享路由事件的定义。
UIElement.MouseUpEvent = Mouse.MouseUpEvent.AddOwner(typeof(UIElement));
引发路由事件
路由事件不是通过传统的.NET事件封装器引发的,而是使用 RaiseEvent() 方法引发的,所有元素都从 UIElement 类继承了该方法。
每个事件处理程序的第一个参数(sender)都提供了引发该事件的对象的引用。第二个参数是 EventArgs 对象,该对象与其他所有可能很重要的附加细节绑定在一起。如果不需要传递额外的细节可使用 RoutedEventArgs.
处理路由事件
可以使用多种方法关联事件处理程序。
<Image Source="hello.jpg" Name = "img" MouseUp="img_MouseUp"/>
// 定义委托对象,并将该委托指向 img_MouseUp() 方法
// 然后将该委托添加到 img.MouseUp 事件的已注册的事件处理程序列表中
img.MouseUp += new MouseButtonEventHandler(img_MouseUp);
// C# 还允许使用更精简的语法,隐式地创建合适的委托对象
img.MouseUp += img_MouseUp;
上面的代码方法依赖事件封装器,事件封装器调用 UIElement.AddHandler() 方法。也可以自行调用 UIElement.AddHanler() 方法直接连接事件。
// 这种方法要创建合适的委托类型(MouseButtonEventHandler),不能隐式地创建委托对象
img.AddHandler(Image.MouseUpEvent, new MouseButtonEventHandler(img_MouseUp));
// 也可以使用定义事件的类的名称,而不是引发事件的类的名称
img.AddHandler(UIElement.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));
如果想断开事件处理程序,只能使用代码,不能使用 XAML。
img.MouseUp -= img_MouseUp;
img.RemoveHandler(Image.MouseUpEvent,new MouseButtonEventHandler(img_MouseUp));
为同一个事件多次连接相同的事件处理程序,通常是错误的结果,这种情况下事件处理程序会被触发多次。如果试图删除已经连接了两次的事件处理程序,事件仍会触发事件处理程序,但只触发一次。
事件路由
<Label BorderThickness="1">
<StackPanel>
<TextBlock Margin="3">
Image and text label
</TextBlock>
<Image Source="hello.jpg"/>
<TextBlock Margin="3">
Courtesy of the StackPanel
</TextBlock>
</StackPanel>
</Label>
上面的标签包含了一个面板,面板里又包含了两块文本和一副图像。单击图像部分会引发 Image.MouseDown 事件,但如果想采用相同方式处理标签上的所有单击事件呢?显然为每个元素的 MouseDown 事件关联同一个处理程序会使得代码杂乱无章切难以维护。
路由事件以下面三种方式出现:
- 与普通.NET事件类似的直接路由事件(direct event).它们源于同一个元素,不传递给其他元素。比如,MouseEnter事件是直接路由事件。
- 在包含层次中向上传递的冒泡路由事件(bubbling event).比如,MouseDown就是冒泡路由事件。该事件首先由被单击的元素引发,接下来被改元素的父元素引发,然后被父元素的父元素引发,以此类推,直到WPF到达元素树的顶部为止。
- 在包含层次中向下传递的隧道事件(tunneling event).隧道路由事件在事件到达恰到的控件之前未预览事件提供了机会。比如,通过PreviewKeyDown可截获是否按下了某个键。首先在窗口级别上,然后是更具体的容器,直至到达当按下键时具有焦点的元素。
当使用 EventManager.RegisterEvent() 方法注册路由事件时,需要传递一个 RoutingStrategy 枚举值,该值用于指示希望应用于事件的事件行为。
MouseUp 和 MouseDown 都是冒泡事件,当单击标签上的图像部分时:
- Image.MouseDown
- StackPanel.MouseDown
- Label.MouseDown
按照嵌套的顺序,一直向上传递到窗口。
RoutedEventArgs 类
在处理冒泡路由事件时,sender参数是对最后哪个链接的引用。如果事件在处理之前,从图像向上冒泡到标签,sender参数就会引用标签对象。
名称 | 说明 |
---|---|
Source | 指示引发了事件的对象。键盘事件-具有焦点的控件;鼠标事件-鼠标下面所有元素中最靠上的元素 |
OriginalSource | 最初引发事件的对象的引用。通常与Source相同 |
RoutedEvent | 通过事件处理程序为触发的事件提供 RoutedEvent 对象。如果同一个处理程序处理不同的事件,这个信息非常有用 |
Handled | 该属性允许终止事件的冒泡或隧道过程。如果设置为 true,事件就不会继续传递,也不会再为其他元素引发该事件 |
处理挂起的事件
按钮(button)会挂起MouseUp事件,并引发更高级的Click事件。同时,Handled标志被设置为 true ,从而阻止MouseUp事件继续传递。
有趣的是,有一种方法可接收被标记为处理过的事件:
// 最后一个参数如果为 true,即使设置了 Handled 标志,也将接收到事件
cmdClear.AddHander(UIElement.MouseUpEvent, new MouseButtonEventHandler(cmdClear_MouseUp),true);
附加事件
<!--StackPanel并没有 Click 事件-->
<StackPanel Button.Click="DoSomething" Margin="5">
<Button>Command 1</Button>
<Button>Command 2</Button>
</StackPanel>
Click事件实际是在 ButtonBase 类中定义的,而Button类继承了该事件。如果为ButtonBase.Click事件关联事件处理程序,那么当单击任何继承自ButtonBase控件(包括Button类、RadioButton类以及CheckBox类)时,都会调用该事件处理程序。如果为 Button.Click事件关联处理程序,只能被Button对象使用。
也可以在代码中关联附加事件,但需要使用 UIElement.AddHandler()方法,而不能使用 += 运算符语法。
stackPanel.AddHandler(Button.Click, new RoutedEventHandler(DoSomething));
这种情况下,怎么区分是哪个按钮触发的事件?可以通过 button的文本,或者Name,也可以设置Tag属性。
<StackPanel Button.Click="DoSomething" Margin="5">
<Button Tag="first button">Command 1</Button>
<Button Tag="second button">Command 2</Button>
</StackPanel>
private void DoSomething(object sender, RoutedEventArgs e)
{
object tag = ((FrameworkElement)sender).Tag;
MessageBox.Show(tag.toString());
}
隧道路由事件
隧道路由事件以单词 Preview 开头,WPF通常成对地定义冒泡路由事件和隧道路由事件。隧道路由事件总在冒泡路由事件之前被触发。如果将隧道路由事件标记为已处理,那就不会再触发冒泡路由事件,因为两个事件共享 RoutedEventArgs类的同一个实例。
如果需要执行一些预处理(根据键盘上特定的键执行动作或过滤掉特定的鼠标动作),隧道路由事件是非常有用的。隧道路由事件的工作方式和冒泡路由事件相同,但方向相反。先在窗口触发,然后再整个层次结构中向下传递,如果在任意为止标记为已处理,就不会发生对应的冒泡事件。
WPF事件
WPF事件通常包括以下5类:
- 生命周期事件:在元素被初始化、加载或卸载时发生这些事件
- 鼠标事件
- 键盘事件
- 手写笔事件:在平板电脑上用手写笔代替鼠标
- 多点触控事件:一根或多跟手指在多点触控屏幕上触摸的结果
声明周期事件
首次创建以及释放所有元素时都会引发事件,它们是在 FrameworkElement 类中定义的。
名称 | 说明 |
---|---|
Initialized | 当元素被实例化,并根据XAML标记设置了元素的属性之后发生。这时元素已经初始化,但窗口的其他部分可能尚未初始化。此外,尚未应用样式和数据绑定。是普通的.NET事件 |
Loaded | 当整个窗口已经初始化并应用了样式和数据绑定时,该事件发生。这是元素呈现之前的最后一站。这时 IsLoaded 为true |
Unloaded | 当元素被释放时,该事件发生,原因时包含元素的窗口被关闭或特定的元素被从窗口中删除 |
FrameworkElement类实现了 ISupportInitialize接口用来控制初始化过程的方法。
- 第一个方法是BeginInit(),在实例化元素后会立即调用该方法。
- 之后XAML解析器设置所有元素的属性并添加内容。
- 第二个方法是 EndInit(),完成初始化后将调用。此时引发Initialized事件
当创建窗口时,会自下而上地初始化每个元素分支。在每个元素都完成初始化后还需要在容器中进行布局、应用样式、绑定到数据源。完成初始化过程就会引发Loaded事件,该过程是自上而下的的方式。当所有元素都引发Loaded事件后窗口就可见了。
可以在窗口构造函数里添加自己的代码,但Loaded事件是更好的选择。因为如果构造函数中发生异常就会在XAML解析器解析页面时抛出该异常。该异常将与InnerException属性中的原始异常一起封装到一个没有用处的 XamlParseException对象中。
键盘事件
名称 | 路由类型 | 说明 |
---|---|---|
PreviewKeyDown | 隧道 | 按下一个键时发生 |
KeyDown | 冒泡 | 按下一个键时发生 |
PreviewTextInput | 隧道 | 当按键完成并且元素正在接收文本输入时发生 |
TextInput | 冒泡 | 当键盘完成并且元素正在接收文本输入时发生 |
PreviewKeyUp | 隧道 | 释放按键发生 |
KeyUp | 冒泡 | 释放按键发生 |
比如对TextBox的输入提供验证操作:
private void textBox_PreviewTextInput(object sender,TextCompositionEventArgs e)
{
short val;
// KeyConverter.ConverterToString()方法,Key.D9 和 Key.NumPad9 都返回字符串 "9"
if(!Int16.TryParse(e.Text,out val))
{
// 只允许输入数字
e.Handled = true;
}
}
private void textBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if(e.Key == Key.Space)
{
// 有一些按键,比如空格,会绕过 PreviewTextInput
e.Handled = true;
}
}
鼠标拖放
拖放操作有两个方面:源和目标。需要在某个为止调用 DragDrop.DoDragDrop()方法来初始化拖放操作,此时确定拖动操作的源,搁置希望移动的内容,并指明允许什么样的拖放效果(复制、移动等)。
private void lb_MouseDown(object sender, MouseButtonEventArgs e)
{
Label lb1 = (Label)sender;
DragDrop.DoDragDrop(lb1, lb1.Content, DragDropEffects.Copy);
}
接收数据的元素需要将它的 AllowDrop 属性设置为 true。
<Label Grid.Row="1" AllowDrop="True" Drop="lbTarget_Drop">To Here</Label>
如果希望有选择的接收内容,可以处理 DragEnter事件。
private void lb2_DragEnter(object sender, DragEventArgs e)
{
if(e.Data.GetDataPresent(DataFromats.Text))
e.Effects = DragDropEffects.Copy;
else
e.Effects = DragDropEffects.None;
}
最后就可以检索并处理数据了。
private void lb2_Drop(object sender, DragEventArgs e)
{
((Label)sender).Content = e.Data.GetData(DataFromats.Text);
}
我的公众号
WPF_05_路由事件的更多相关文章
- WPF的路由事件、冒泡事件、隧道事件(预览事件)
本文摘要: 1:什么是路由事件: 2:中断事件路由: 3:自定义路由事件: 4:为什么需要自定义路由事件: 5:什么是冒泡事件和预览事件(隧道事件): 1:什么是路由事件 WPF中的事件为路由事件,所 ...
- WPF学习之路由事件
原文:http://www.cnblogs.com/lxy131/archive/2010/08/10/1796754.html WPF中新添加了一种事件---路由事件 路由事件与一般事件的区别在于: ...
- WPF:自定义路由事件的实现
路由事件通过EventManager,RegisterRoutedEvent方法注册,通过AddHandler和RemoveHandler来关联和解除关联的事件处理函数:通过RaiseEvent方法来 ...
- 学习WPF——了解路由事件
入门 我们先来看一个例子 前台代码: 后台代码: 点击按钮的运行效果第一个弹出窗口 第二个弹出窗口: 第三个弹出窗口: 说明 当点击按钮之后,先触发按钮的click事件,再上查找,发现stackpan ...
- WPF 路由事件总结
1.什么是路由事件 已下为MSDN中的定义 功能定义:路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件. 实现定义:路由事件是一个 CLR 事件,可以由 R ...
- .NET: WPF 路由事件
(一)使用WPF内置路由事件 xaml: <Window x:Class="WpfApplication1.MainWindow" xmlns="http://sc ...
- 【WPF】路由事件
总结WPF中的路由事件,我将学到的内容分为四部分来逐渐掌握 第一部分:wpf中内置的路由事件 以Button的Click事件来说明内置路由事件的使用 XAML代码: <Window x:Clas ...
- WPF 自定义路由事件
如何:创建自定义路由事件 首先自定义事件支持事件路由,需要使用 RegisterRoutedEvent 方法注册 RoutedEvent C#语法 public static RoutedEvent ...
- Wpf自定义路由事件
创建自定义路由事件大体可以分为三个步骤: ①声明并注册路由事件. ②为路由事件添加CLR事件包装. ③创建可以激发路由事件的方法. 以ButtonBase类中代码为例展示这3个步骤: public a ...
随机推荐
- ecshop调用指定栏目下的文章的方法
打开 index.php 添加 fun函数一个,需放在<php与?>中间. /** * 获得指定栏目的文章列表. * @param int $cid 栏目ID * @param int $ ...
- Python列表操作常用API
1.列表的概念 (1)列表的定义 列表是Python中一种基本的数据结构.列表存储的数据,我们称为元素.在列表中的每个元素都会有一个下标来与之对应,第一个索引是0,第二个索引是1,依此类推的整数. 列 ...
- .Net微服务实战之可观测性
系列文章 .Net微服务实战之技术选型篇 .Net微服务实战之技术架构分层篇 .Net微服务实战之DevOps篇 .Net微服务实战之负载均衡(上) .Net微服务实战之CI/CD .Net微服务实战 ...
- 支持remote write和exemplar的prometheus服务
最近项目组在做Prometheus指标采集和告警,其中用到了Prometheus的exemplar特性,由于该特性比较新,当前支持该特性的存储有比较少.因此需要自行实现exemplar功能. 我在gi ...
- 10.11 HTTPS
没有HTTPS的抓包截图 HTTPS=HTTP + TLS/SSL https 实现过程如下 1.客户端发起HTTPS请求 rewrite www.baidu.com https://www.baid ...
- instanceof和类型转换
什么是instanceof 判断一个对象是什么类型 注意点 X 和 Y 必须要有父子关系 否则编译都会失败 X对象只要是Y的子类(无论 是 儿子 还是 孙子 还是 曾孙....)X instanceo ...
- Linux下Electron loadURL报错 ERR_FAILED(-2) Not allowed to load local resource
Linux下Electron loadURL报错 ERR_FAILED(-2) Not allowed to load local resource 背景 使用electron-vue的时候,窗体创建 ...
- HTTP基础系列之:一文搞懂URL
一般我们日常在上网的时候,会在浏览器的地址栏里输入一个网站的 "网址",点击下回车,就会跳到你想去的网站,就类似这样 但其实,叫做 "网址" 并不是特别的准确, ...
- 某个buuctf的题(easy_tornado)
题目:http://88099f53-12b6-470a-9993-b73e4155940e.node3.buuoj.cn/ 1首先看三个文件的内容 2简单分析 如果出题人没整一些花里胡哨的,那么fl ...
- 在Vue&Element前端项目中,对于字典列表的显示处理
在很多项目开发中,我们为了使用方便,一般都会封装一些自定义组件来简化界面的显示处理,例如参照字典的下拉列表显示,是我们项目中经常用到的功能之一,本篇随笔介绍在Vue&Element前端项目中如 ...