WPF中的隧道路由和冒泡路由事件
文章目录
- 简介:
- 一、事件最基本的用法
- 二、理解路由事件
简介:
WPF中使用路由事件升级了传统应用开发中的事件,在WPF中使用路由事件能更好的处理事件相关的逻辑,我们从这篇开始整理事件的用法和什么是直接路由,什么是冒泡路由,以及什么是隧道路由。
一、事件最基本的用法
在基于事件驱动的开发中,把代码放在响应注册的事件的处理函数内,比如Click事件、MouseDown事件、MouseUp事件等等。每个控件响应自己的注册事件,有很多如果在事件上有相互关联和影响的事件,就要在一个业务逻辑里写比较多的代码。而路由事件主要的优势就是路由事件可以在元素树上进行传递,并且沿着元素树的传播途径被事件处理程序处理。这样我们写代码的过程中时就可以更好的组织代码到合适的位置。
WPF事件模型和WPF属性模型非常类似,与依赖项属性一样,路由事件由只读的静态字段表示,在静态构造函数中注册,并通过标准的.NET事件定义进行封装。这里我们只讲如何更好的使用。原理部分请看源码。比如ButtonBase提供的Click事件。
<Button Content="事件处理程序" Click="Button_Click"/>
private void Button_Click(object sender, RoutedEventArgs e)
{//这是Click事件处理程序代码部分。
}
在注册事件后,在事件处理程序中第一个参数 sender提供引发该事件的对象,第二个参数是EventArgs对象。在WPF中如果事件不需要传递额外的信息,可以使用RoutedEventArgs类,如果需要传递额外的信息,就要是有继承自RoutedEventArgs的对象。比如处理inkcanvas墨迹绘制的。比如处理多点触控的。这些都是变相继承RoutedEventArgs类。里面会包含在这种场景下更加多的信息。

注册事件的几种写法:
1)在XAML代码中<Button x:Name="EventMessageButton" Content="事件处理程序" MouseUp="EventMessageButton_MouseUp"/>
2)在cs代码中 EventMessageButton.MouseUp += EventMessageButton_MouseUp;
3)在cs代码中 EventMessageButton.MouseUp += new MouseButtonEventHandler(EventMessageButton_MouseUp);private void EventMessageButton_MouseUp(object sender, MouseButtonEventArgs e)
{//我是处理程序。
}
第一种写法: 我们使用XAML文件中在Button元素内使用MouseUp来创建后台事件处理代码 Btn_eventMessge_MouseUp
第二种写法: 我们在后台代码中使用MouseUp+=的方式注册。一种是New MouseButtonEventHandler传入方法名。一种是匿名的直接传入方法名,这三种注册方式达成的效果是一样的。
而这三种实际上使用的是事件封装器。另一种方式是通过使用UIElement.AddHandler来直接连接事件。这里看个人习惯把。但是各种写法主要解决的问题还是解耦,因为这些会关联到后面的命令,动画。模板。触发器。MVVM下的使用,等等。这是个比较长久的问题。所以在这里,能够使用,看得明白,目前这个阶段就可以了。
我们继续往下。解除关联
在注册事件的时候,最好先使用-=来解除关联,避免多次触发不合符预期的监听事件。断开使用-=或者使用UIElement.RemoveHandler来解除关联。 因为事件在多次+=注册事件处理程序是可行的。而事件的多词解除关系不会引发任何问题,因此不要担心+=和-=不匹配的问题。
public MainWindow(){InitializeComponent();EventMessageButton.MouseUp -= EventMessageButton_MouseUp; EventMessageButton.MouseUp += EventMessageButton_MouseUp;}
二、理解路由事件
我们知道了事件可以在元素上注册事件处理程序,那么我们知道内容控件是可以相互嵌套各种奇奇怪怪的组合以达到自己想要的效果,在这种情况下我们假设一个比较常见的场景。我们有一个标签,标签中包含一个StackPanel面板,面板中包含一幅图片和2个文本。

<Label BorderBrush="Black" BorderThickness="1"><StackPanel><TextBlock Margin="3">我是图片标题</TextBlock><Image Source="1.png" Stretch="None"/><TextBlock Margin="3">我是图片正文</TextBlock></StackPanel>
</Label>
我们的控件来回嵌套内容结构很复杂了。但是我们想在用户点击时只在一个地方响应我们的代码。如果为每个元素都关联同一个事件处理程序,代码会很乱。而且难以维护。而路由事件就是为了解决这个问题的。路由事件分为三种:
1)和普通的.NET事件类似,直接路由事件(direct event) 他们源于一个元素,不传递给其他元素,比如MouseEnter事件,是直接路由事件。
2)向上传递的冒泡路由事件(bubbling event)比如MouseDown事件,是冒泡路由事件,该事件先由被单击的元素引发,接下来被该元素的父元素引发,然后被父元素的父元素引发,以此类推。直到元素树的顶部。
3)向下传递的隧道路由事件(tunneling event) 比如PreviewKeyDown事件,隧道路由事件在事件到达恰当的空间之前为预览事件和终止事件提供了机会,比如PreviewKeyDown事件可以截获是否按下了某个键,首先在窗口级别上,然后是更具体的容器,直到当按下时具有焦点的元素。
当使用EventManager.RegisterEvent()发给发注册路由事件时,需要传递一个RoutingStrategy枚举,指示希望用于事件的事件行为。
MouseUP和MouseDown事件都是冒泡路由事件,因此当在上面的图片中按下鼠标左键后顺序触发MouseDown事件的顺序是冒泡的。我们使用Snoop软件抓取一下过程:

从图中我们看到首先触发的是PreviewMouseDown的隧道路由。他可以让我们有机会预览事件或终止事件。我们看到了从MainWindow开始到最终的Image结束。我们没有终止路由。所以进行了下一轮的冒泡路由。从Image开始到MainWindow。
整个流程就结束了。我们看到路由事件提供了对事件处理非常丰富的功能。具体的隧道或冒泡行为可以参考RoutedEventArgs中的内容。
Source 属性是引发事件的的对象。 OriginalSource是最初是什么对象引发了事件。RoutedEvent为触发的事件提供的RoutedEvent对象。里面是需要用到的当前的参数,比如鼠标坐标,touch等等。Handled属性的作用是终止事件是否继续传递。
我们现在开始在这个例子上添加代码。用于演示我们怎么处理冒泡路由。
<Window x:Class="WPFEvent.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WPFEvent"mc:Ignorable="d" MouseUp="EventResponseProcess_MouseUp"Title="MainWindow" Height="450" Width="800"><Grid Margin="3" MouseUp="EventResponseProcess_MouseUp"><Grid.RowDefinitions><RowDefinition Height="Auto"></RowDefinition><RowDefinition Height="*"></RowDefinition><RowDefinition Height="Auto"></RowDefinition><RowDefinition Height="Auto"></RowDefinition></Grid.RowDefinitions><Label Margin="5" Grid.Row="0" HorizontalAlignment="Left" Background="AliceBlue" BorderBrush="Black" BorderThickness="1" MouseUp="EventResponseProcess_MouseUp"><StackPanel MouseUp="EventResponseProcess_MouseUp"><TextBlock Margin="3" MouseUp="SomethingClicked">我是图片标题</TextBlock><Image Source="1.png" Stretch="None" MouseUp="EventResponseProcess_MouseUp"/><TextBlock Margin="3">我是图片正文</TextBlock></StackPanel></Label><ListBox Grid.Row="1" Margin="5" Name="MessageListBox"></ListBox><CheckBox Grid.Row="2" Margin="5" Name="HandlerCheckBox">Handle first event</CheckBox><Button Grid.Row="3" Margin="5" Padding="3" HorizontalAlignment="Right" Name="ClearButton" Click="ClearButton_Click">Clear List</Button></Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 WPFEvent
{/// <summary>/// MainWindow.xaml 的交互逻辑/// </summary>public partial class MainWindow : Window{public MainWindow(){InitializeComponent();}protected int eventCounter = 0;private void EventResponseProcess_MouseUp(object sender, MouseButtonEventArgs e){eventCounter++;string message = $"#{ eventCounter}:\r\n Sender: {sender} \r\n Source: {e.Source} \r\n Original Source: {e.OriginalSource}"; MessageListBox.Items.Add(message);e.Handled = (bool)HandleCheckBox.IsChecked;}private void ClearButton_Click(object sender, RoutedEventArgs e) { }}
}
和上图一样,我们尝试观察执行过程。看下触发过程,我们就能了解这个冒泡路由的工作过程了。上面写的这个例子。主要是让我们熟悉对于事件传入参数OriginalSource的使用。勾选界面上的HandleCheckBox复选框可以终止冒泡事件,从而只触发第一个Image的事件。可以自己写代码尝试一下整个过程。
有个方法可以接收终止的事件消息,使用AddHandler()重载的方法。

这里还有一个常用的技巧,事件的附加。
如下面的代码,在StackPanel中不存在Button的Click事件,但是可以通过ButtonBase.Click获取按钮的点击事件,此事件将会在StackPanel容器里面的任意按钮被点击时触发。
<StackPanel ButtonBase.Click="StackPanel_Click" Margin="5"><Button Content="按钮A" Click="Button_Click"/><Button Content="按钮B" Click="Button_Click"/><Button Content="按钮C" Click="Button_Click"/>
</StackPanel>
隧道路由这里就不写了,前面已经讲过。隧道路由命名都是Preview开头的。隧道路由全部结束了之后,同级的冒泡路由才开始。隧道路由主要是做预先处理,可以停止路由事件,也可以在事件处理程序中写一些对应的处理代码。
相关文章:
WPF中的隧道路由和冒泡路由事件
文章目录 简介:一、事件最基本的用法二、理解路由事件 简介: WPF中使用路由事件升级了传统应用开发中的事件,在WPF中使用路由事件能更好的处理事件相关的逻辑,我们从这篇开始整理事件的用法和什么是直接路由,什么是冒…...
ISO七层模型 tcp/ip
OSI七层模型(重点例子) OSI(Open Systems Interconnection)模型,也称为开放系统互连模型,是一个理论模型,由国际标准化组织(ISO)制定,用于描述和理解不同网络…...
MySQL的三种重要的日志
日志 Mysql有三大日志系统 Undo Log(回滚日志):记录修改前的数据,用于事务回滚和 MVCC(多版本并发控制)。 Redo Log(重做日志):记录数据变更,用于崩溃恢复&…...
神经网络学习2
张量(Tensor)是深度学习和科学计算中的基本数据结构,用于表示多维数组。张量可以看作是一个更广义的概念,涵盖了标量、向量、矩阵以及更高维度的数据结构。具体来说,张量的维度可以是以下几种形式: 标量&am…...
Spring Boot整合Redis通过Zset数据类型+定时任务实现延迟队列
😄 19年之后由于某些原因断更了三年,23年重新扬帆起航,推出更多优质博文,希望大家多多支持~ 🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Mi…...
Android入门第69天-AndroidStudio中的Gradle使用国内镜像最强教程
背景 AndroidStudio默认连接的是dl.google的gadle仓库。 每次重新build时: 下载速度慢;等待了半天总时build faild;build到一半connection timeout;即使使用了魔法也难以一次build好;这严重影响了我们的学习、开发效率。 当前网络上的使用国内镜像的教程不全 网上的教程…...
深入浅出 Qt 中 QListView 的设计思想,并掌握大规模、高性能列表的实现方法
在大规模列表控件的显示需求中,必须解决2个问题才能获得较好的性能: 第一就是数据存在哪里, 避免出现数据的副本。第二就是如何展示Item,如何复用或避免创建大量的Item控件。 在QListView体系里,QAbstractListModel解…...
课设--学生成绩管理系统
欢迎来到 Papicatch的博客 文章目录 🍉技术核心 🍉引言 🍈标识 🍈背景 🍈项目概述 🍈 文档概述 🍉可行性分析的前提 🍈项目的要求 🍈项目的目标 🍈…...
MySQL性能分析
一、查看执行频率 sql执行频率,执行下述指令可以看到select,update,delete等操作的次数 show global status like Com_______; 具体我们在终端登录mysql看下,使用下述命令登录mysql,并输入命令 mysql -u 用户名 -p 上述查询,删…...
为什么要学习Flink系统管理及优化课程?
Flink系统是一种流式处理框架,能够高效地处理大规模数据流。然而,要确保Flink系统的正常运行,就需要进行系统管理和优化。系统管理是指对Flink集群的监控、调度和维护,而系统优化则是指通过调整参数和优化算法,提高Fli…...
把本机的bash构建到docker镜像里面
最近突发奇想,想把本机的bash放到docker镜像里面,接下来看操作。 获取bash以及依赖 [rootbogon ~]# cat get_lib_info.sh #!/bin/bash# 函数:显示帮助信息 show_help() {echo "Usage: $(basename "$0") -h -f <file>…...
【数据分析】推断统计学及Python实现
各位大佬好 ,这里是阿川的博客,祝您变得更强 个人主页:在线OJ的阿川 大佬的支持和鼓励,将是我成长路上最大的动力 阿川水平有限,如有错误,欢迎大佬指正 Python 初阶 Python–语言基础与由来介绍 Python–…...
探索交互的本质:从指令到界面的演进与Linux基础指令的深入剖析
目录 1.指令 vs 界面//选读 1.1交互的需求 满足需求的第一阶段-指令 满足需求的第二阶段-界面 1.2 指令 和 界面交互 区别 2.操作系统介绍 2.1 举例说明 驱动软件层 2.2 为什么要有操作系统? 0x03 为什么要进行指令操作? 3.Linux基本指令 l…...
uniapp vue分享功能集成
分享必须通过button设置open-type"share"拉起 <view class"img horizontal center" style"margin-right: 20rpx;"><image class"img" :src"src" click"onTapClick(xxx)" style"z-index: 1;" …...
软件工程实务:软件产品
目录 1、软件产品的基本概念 2、软件工程是什么? 为什么产生软件工程? 软件工程是做什么的? 3、定制软件和软件产品的工程比较 4 、软件产品的运行模式 5、软件产品开发时需要考虑的两个基本技术因素 6、产品愿景 7、软件产品管理 8、产品原型设计 9、小结…...
带侧边栏布局:带导航的网页
目录 任务描述 相关知识 HTML(HyperText Markup Language) CSS(Cascading Style Sheets): 编程要求 任务描述 在本关中,你的任务是创建一个带侧边栏和导航的网页布局。这种布局通常用于网站或应用程序,其中侧边栏…...
react学习-redux快速体验
1.redux是用于和react搭配使用的状态管理工具,类似于vue的vuex。redux可以不和任何框架绑定,独立使用 2.使用步骤 (1)定义一个reducer函数(根据当前想要做的修改返回一个新的状态) (2࿰…...
基于flask的网站如何使用https加密通信-问题记录
文章目录 项目场景:问题1问题描述原因分析解决步骤解决方案 问题2问题描述原因分析解决方案 参考文章 项目场景: 项目场景:基于flask的网站使用https加密通信一文中遇到的问题记录 问题1 问题描述 使用下面的命令生成自签名的SSL/TLS证书和…...
记C#优化接口速度过程
前提摘要 首先这个项目是接手的前一任先写的项目,接手后,要求对项目一些速度相对较慢的接口进行优化,到第一个速度比较慢的接口后,发现单接口耗时4-8秒,是的,请求同一个接口,在参数不变的情况下…...
windows环境如何运行python/java后台服务器进程而不显示控制台窗口
1.通常我们在windows环境下使用Java或Python语言编写服务器程序,都希望他在后台运行,不要显示黑乎乎的控制台窗口: 2.有人写了一个bat文件: cd /d D:\lottery\server && python .\main.py 放到了开机自启动里,可是开机的…...
从零构建开源语音AI交互中枢:EchoKit Server部署与调优指南
1. 项目概述:构建你自己的语音AI交互中枢 如果你对智能音箱、语音助手这类设备感兴趣,但又觉得市面上的产品要么功能封闭,要么隐私堪忧,那么今天聊的这个项目——EchoKit Server,可能会让你眼前一亮。简单来说&#x…...
Pega Helm Charts:Kubernetes上企业级低代码BPM平台部署指南
1. 项目概述:Pega Helm Charts 是什么,以及为什么你需要它如果你正在或计划在 Kubernetes 上部署 Pega Platform,那么pegasystems/pega-helm-charts这个项目就是你绕不开的“官方说明书”和“自动化部署工具箱”。简单来说,这是一…...
GitHub 被分号击穿信任防线,AI 逆向工具敲响闭源系统安全警钟
GitHub 被分号击穿三层信任,AI 填平逆向护城河敲响闭源系统安全警钟 2026 年 3 月 4 日,GitHub 收到 Wiz 通过 Bug Bounty 提交的报告,报告描述的攻击入口极其简单:一条构造过的 git push,带一个 push optionÿ…...
SyntaxUI:基于Tailwind CSS与Framer Motion的React组件库实战指南
1. 项目概述:SyntaxUI,一个为现代Web开发者提速的组件库如果你和我一样,常年奋战在React、Next.js项目的一线,那你一定对“重复造轮子”这件事深恶痛绝。每次新项目启动,从零开始搭建按钮、卡片、模态框、导航栏&#…...
为什么你需要m4s-converter:让B站缓存视频重获自由的秘密武器
为什么你需要m4s-converter:让B站缓存视频重获自由的秘密武器 【免费下载链接】m4s-converter 一个跨平台小工具,将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 你是否曾经遇到过这样的…...
Midjourney生成图落地PS的7大断层痛点:从提示词对齐、分辨率陷阱到图层级精修,一文打通AI与专业图像处理全链路
更多请点击: https://intelliparadigm.com 第一章:Midjourney与Photoshop整合方案的底层逻辑与工作流重构 Midjourney 生成的图像虽具高美学质量,但缺乏图层控制、非破坏性编辑及像素级精度,而 Photoshop 正是弥补这一缺口的核心…...
模拟仿真技术在现代集成电路设计中的挑战与解决方案
1. 模拟仿真技术面临的现代挑战在当今集成电路设计领域,模拟仿真技术正面临前所未有的挑战。随着工艺节点从130nm一路演进到15nm甚至更小尺寸,设计复杂度呈指数级增长。我曾参与过多个采用28nm工艺的混合信号芯片项目,深刻体会到传统SPICE仿真…...
【信息科学与工程学】【安全领域】第二十七篇 几何学在网络安全的应用(1)
网络安全中的几何学应用全景 一、几何学与网络安全的核心联系框架 1.1 几何思维在网络安全的映射 几何概念 网络安全映射 安全价值 应用本质 空间与距离 特征空间、异常距离 相似性度量、异常检测 量化“正常”与“异常”的距离 拓扑结构 网络连接图、攻击路径 …...
避开BUUCTF《Life on Mars》的思维陷阱:当information_schema查询结果‘不对劲’时,你的排查清单应该有哪些?
破解BUUCTF《Life on Mars》的数据库迷局:当information_schema说谎时的七种侦查策略 在CTF赛场上,SQL注入类题目往往不会按教科书上的剧本发展。当你在BUUCTF《Life on Mars》这道题中执行group_concat(database()) from information_schema.schemata却…...
Arm CoreLink GFC-200 Flash控制器架构与优化实践
1. Arm CoreLink GFC-200 Flash控制器架构解析在嵌入式系统设计中,非易失性存储管理是核心挑战之一。作为Arm CoreLink系列的重要成员,GFC-200通用Flash控制器通过创新的总线架构和分区管理机制,为SoC设计提供了高效的Flash存储解决方案。这款…...
