WPF中的隧道路由和冒泡路由事件

news2025/2/24 18:04:40

文章目录

  • 简介:
  • 一、事件最基本的用法
  • 二、理解路由事件


简介:

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开头的。隧道路由全部结束了之后,同级的冒泡路由才开始。隧道路由主要是做预先处理,可以停止路由事件,也可以在事件处理程序中写一些对应的处理代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1824210.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【建设方案】文档管理系统实现方案(Word原件)

文档管理系统建设的主要意义在于提升组织内部文档管理的效率、安全性和便利性。首先&#xff0c;通过集中存储和分类管理&#xff0c;文档管理系统能够迅速检索和共享文件&#xff0c;大幅提高工作效率。其次&#xff0c;系统内置的权限控制功能确保文档的安全&#xff0c;防止…

OpenStack入门体验及一键部署

OpenStack入门体验 技能目标&#xff1a; 了解云计算概念 了解OpenStack 了解OpenStack的构成 会OpenStack单机环境一键部署 从控制台认识OpenStack各项功能会 通过OpenStack控制台创建云主机 什么是云计算 云计算(cloudcomputing)是一种基于网络的超级计算模式&a…

Nginx负载均衡之长连接负载均衡

当客户端通过浏览器访问 HTTP 服务器时&#xff0c;HTTP 请求会通过 TCP 协议与 HTTP 服务器建立一条访问通道&#xff0c;当本次访问数据传输完毕后&#xff0c;该 TCP 连接会立即被断开&#xff0c;由于这个连接存在的时间很短&#xff0c;所以 HTTP 连接也被称为短连接。 …

Python学习打卡:day06

day6 笔记来源于&#xff1a;黑马程序员python教程&#xff0c;8天python从入门到精通&#xff0c;学python看这套就够了 目录 day648、函数综合案例49、数据容器入门50、列表的定义语法51、列表的下标索引1、列表的下标&#xff08;索引&#xff09;2、列表的下标&#xff08…

2024 年最新使用 Node 搭建QQ开放平台官方 QQ 频道机器人详细教程(更新中)

注册 QQ 开放平台账号 QQ 开放平台是腾讯应用综合开放类平台&#xff0c;包含 QQ 机器人、QQ 小程序、QQ 小游戏 等集成化管理&#xff0c;也就是说你注册了QQ 开放平台&#xff0c;你开发 QQ 机器人还是 QQ 小程序都是在这个平台进行部署上线和管理。 如何注册 QQ 开放平台账…

代码随想录:回溯20-21

51.N皇后 题目 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回所有不同的 n 皇后问题 的解…

WDF驱动开发-同步技术

使用自动同步 基于框架的驱动程序中几乎所有的代码都驻留在事件回调函数中。 框架会自动同步驱动程序的大部分回调函数&#xff0c;如下所示&#xff1a; 框架始终将 常规设备对象、 功能设备对象 (FDO) 和 物理设备对象 (PDO) 事件回调函数同步&#xff0c;以便每个设备一次…

内网安全【2】-域防火墙

1.判断什么时候用代理 2.判断什么时候用隧道 3.判断出网和不出网协议 4.如何使用代理建立节点并连接 5.如何使用隧道技术封装协议上线 6.判断哪些代理或隧道情况选择放弃 代理技术&#xff1a;解决网络通讯不通的问题(利用跳板机建立节点后续操作)&#xff08;网络设置导…

【SpringBoot】深入分析 SpringApplication 源码:彻底理解 SpringBoot 启动流程

在黄昏的余晖里&#xff0c;梦境渐浓&#xff0c;如烟如雾。心随星辰&#xff0c;徜徉远方&#xff0c;岁月静好&#xff0c;愿如此刻般绵长。 文章目录 前言一、SpringBoot 应用二、SpringApplication2.1 SpringApplication 中的属性2.2 SpringApplication 的构造器2.3 Sprin…

高压消防接力泵的工作原理_鼎跃安全

森林消防工作是一项艰巨的任务&#xff0c;森林火灾具有蔓延快、控制难和燃烧剧烈等特点&#xff1b;同时&#xff0c;森林具有复杂的峡谷、山坡和陡峭等复杂情况&#xff0c;传统的消防设备难以深入火场&#xff0c;高压消防接力泵通过便携灵活性&#xff0c;深入火场助力消防…

【TF-IDF算法】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

基于LangChain-Chatchat实现的RAG-本地知识库的问答应用[2]-简洁部署版

基于LangChain-Chatchat实现的RAG-本地知识库的问答应用[2]-简洁部署版 1.环境要求 1.1 软件要求 要顺利运行本代码,请按照以下系统要求进行配置 已经测试过的系统 Linux Ubuntu 22.04.5 kernel version 6.7其他系统可能出现系统兼容性问题。 最低要求 该要求仅针对标准模…

C++的map

作用&#xff1a; 映射&#xff0c;相当于python的字典&#xff0c;使用一个key来寻找value&#xff0c;m[key]value; 生成&#xff1a; map<int,string> m;//无参生成&#xff0c;key是int类型&#xff0c;value是string类型 map<int,string> m{{1,"hello…

手把手教你入门vue+springboot开发(三)--登录功能后端

文章目录 前言一、redis安装二、后端代码1.修改application.yml文件2.增加utils文件3.增加Result类4.修改UserController类5.修改UserMapper类6.修改UserService和UserServiceImpl类7.增加LoginInterceptor类8.增加WebConfig类9.修改pom.xml文件 前言 前两篇我们用vuespringbo…

内网不能访问网站怎么办?

内网不能访问网站是在网络使用过程中常见的问题之一。当我们使用局域网连接时&#xff0c;有时候会遇到无法访问特定网站的情况。这可能是因为网络环境复杂&#xff0c;或者受到了某些限制。本篇文章将介绍一种解决内网不能访问网站问题的产品——天联组网。 天联组网是一款由…

非计算机专业可以考“软考”吗?

全国计算机软件水平考试对报名条件没有学历、资历、年龄以及专业等限制&#xff0c;非计算机专业的人员也可以报考。证书长期有效&#xff0c;考生可根据个人需求选择合适的级别和资格进行报考。报名方式包括网上报名和考生本人到指定地点报名两种。 考试范围 (1) 高级资格包括…

RS485和CAN电路中的TVS管选择

在RS485和CAN电路设计中&#xff0c;经常要考虑“静电和浪涌保护”&#xff0c;怎么选择TVS管&#xff0c;很少有人讲解。 1、先了解TVS管 TVS管有单向管和双向管&#xff0c;通常后缀为CA的是双向TVS管&#xff0c;只有字母A的是单向TVS管。见下图&#xff1a; 2、TVS选择依…

C++11列表初始化{}

列表初始化 C11后为了能让自定义类型也能够快速被初始化新增 {} 内置类型变量 int a1 { 10 };int a2{ 11 };int a3 { 1 2 };int a4{ 1 2 }; 注意&#xff1a;列表初始化可以在{}之前使用等号&#xff0c;其效果与不使用没有什么区别。 内置类型数组 int arr1[] { 1,2,3…

安装前端依赖node-sass报错

文章目录 问题1&#xff1a;node-sass报错问题2&#xff1a;node-gyp报错问题3&#xff1a;node-sass再次报错问题4&#xff1a;node-sass三次报错 问题1&#xff1a;node-sass报错 问题描述&#xff1a;经常会碰到一个新的项目安装依赖时&#xff0c;会报node-sass版本的问题…

白嫖Cloudflare Workers 搭建 Docker Hub镜像加速服务

简介 基于Cloudflare Workers 搭建 Docker Hub镜像加速服务。 首先要注册一个Cloudflare账号。 Cloudflare账号下域名的一级域名&#xff0c;推荐万网注册个top域名&#xff0c;再转移到Cloudflare&#xff0c;很便宜的。 注意 Worker 每天每免费账号有次数限制&#xff0c;…