我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

这篇文章只是熟悉什么是路由事件,了解什么是冒泡路由、什么是隧道路由。所以理解这些是什么事件,这些事件在如何工作就可以了。后面会讲到Window类的生命周期。才会去深入分析WPF下的事件过程。

以上就是详解WPF中的隧道路由和冒泡路由事件的详细内容,更多关于WPF中的隧道路由和冒泡路由事件的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

WPF中隧道路由和冒泡路由事件的示例分析

这篇文章主要介绍了WPF中隧道路由和冒泡路由事件的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。  WPF中使用路由事件升级了传统应用开发中的事件,在WPF中使用路由
2023-06-14

React中路由的参数传递路由的配置文件详解

路由的配置文件目前我们所有的路由定义都是直接使用Route组件,并且添加属性来完成的,路由的参数传递有二种方式这,两种方式在Router6.x中都是提供的hook函数的API, 类组件需要通过高阶组件的方式使用,本文通过示例代码详解讲解,需要的朋友参考下吧
2022-11-13

React中的路由嵌套和手动实现路由跳转的方式详解

这篇文章主要介绍了React中的路由嵌套和手动实现路由跳转的方式,手动路由的跳转,主要是通过Link或者NavLink进行跳转的,实际上我们也可以通JavaScript代码进行跳转,需要的朋友可以参考下
2022-11-13

一文详解Node中的Express和路由模块

本篇文章带大家一起学习Node,深入介绍一下Express和路由模块的使用方法,希望对大家有所帮助!
2023-05-14

详解vue中路由的安装和使用方法

Vue 是一款流行的前端开发框架,它提供了诸多方便开发的工具和功能。其中,Vue 路由能够帮助我们创建单页应用程序,并且帮助我们实现路由功能。本篇文章将会介绍 Vue 路由的使用。Vue路由安装在开始使用 Vue 路由之前,我们需要先安装 Vue Router。可以通过 npm 安装:```npm install vue-router```Vue路由器的使用在 Vue 项目的入
2023-05-14

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录