【WPF.NET开发】将路由事件标记为已处理和类处理

本文内容

  1. 先决条件
  2. 何时将路由事件标记为已处理
  3. 预览和浮升路由事件对
  4. 实例和类路由事件处理程序
  5. 复合控件中的输入事件禁止

尽管对于何时将路由事件标记为已处理没有绝对规则,但如果代码以重要方式响应事件,请考虑将事件标记为已处理。 标记为已处理的路由事件会继续进行其路由,但只会调用配置为响应已处理事件的处理程序。 基本上,将路由事件标记为已处理会限制其在事件路由上对侦听器的可见性。

路由事件处理程序可以是实例处理程序或类处理程序。 实例处理程序处理对象或 XAML 元素上的路由事件。 类处理程序在类级别处理路由事件,会在任何实例处理程序对类的任何实例响应相同事件之前进行调用。 当路由事件标记为已处理时,它们通常会在类处理程序中标记为这样。 本文讨论了将路由事件标记为已处理的好处和潜在缺陷、不同类型的路由事件和路由事件处理程序以及复合控件中的事件禁止。

1、先决条件

本文假定你对路由事件有基本的了解,并且已阅读路由事件概述
。 若要遵循本文中的示例,如果熟悉 Extensible Application Markup Language (XAML) 并知道如何编写 Windows Presentation Foundation (WPF) 应用程序,将会很有帮助。

2、何时将路由事件标记为已处理

通常,只应有一个处理程序为每个路由事件提供重要响应。 避免使用路由事件系统跨多个处理程序提供重要响应。 构成重要响应的定义是主观的,取决于应用程序。 一般准则是:

  • 重要响应包括设置焦点、修改公共状态、设置影响视觉表示形式的属性、引发新事件以及完全处理事件。
  • 不重要响应包括修改私有状态(而没有视觉或编程影响)、事件日志记录以及检查事件数据而不响应事件。

某些 WPF 控件通过将不需要进一步处理的组件级别事件标记为已处理来禁止这些事件。 

若要将事件标记为已处理,请在其事件数据中将 Handled 属性值设置为 true。 尽管可以将该值还原到 false,但很少需要这样做。

3、预览和浮升路由事件对


预览和浮升路由事件对特定于输入事件
。 多个输入事件实现隧道和浮升路由事件对,例如 PreviewKeyDown 和 KeyDown。 Preview 前缀表示一旦预览事件完成,浮升事件便会启动。 每个预览和浮升事件对会共享事件数据的相同实例。

路由事件处理程序按对应于事件路由策略的顺序进行调用:

  1. 预览事件从应用程序根元素向下传递到引发路由事件的元素。 附加到应用程序根元素的预览事件处理程序会首先进行调用,接下来是附加到后续嵌套元素的处理程序。
  2. 预览事件完成后,配对的浮升事件会从将路由事件引发的元素传递到应用程序根元素。 附加到引发路由事件的相同元素的浮升事件处理程序会首先进行调用,接下来是附加到后续父元素的处理程序。

配对的预览和浮升事件是声明并引发自身路由事件的多个 WPF 类的内部实现的一部分。 如果没有该类级别内部实现,预览和浮升路由事件会完全独立,不会共享事件数据(无论事件命名如何)。 

由于每个预览和浮升事件对共享事件数据的相同实例,因此如果预览路由事件标记为已处理,则其配对的浮升事件也会进行处理。 如果浮升路由事件标记为已处理,则不会影响配对的预览事件,因为预览事件已完成。 将预览和浮升输入事件对标记为已处理时要小心。 已处理的预览输入事件不会为隧道路由的其余部分调用任何正常注册的事件处理程序,并且不会引发配对的浮升事件。 已处理的浮升输入事件不会为浮升路由的其余部分调用任何正常注册的事件处理程序。

4、实例和类路由事件处理程序

路由事件处理程序可以是实例处理程序或类处理程序。 给定类的类处理程序会在任何实例处理程序对该类的任何实例响应相同事件之前进行调用。 由于此行为,当路由事件标记为已处理时,它们通常会在类处理程序中标记为这样。 有两种类型的类处理程序:


  • 静态类事件处理程序,通过在静态类构造函数中调用 RegisterClassHandler 方法进行注册。

  • 重写类事件处理程序,通过重写基类虚拟事件方法进行注册。 基类虚拟事件方法的存在主要是用于输入事件,名称以 On<事件名称> 和 OnPreview<事件名称> 开头。

4.1 实例事件处理程序

可以通过直接调用 AddHandler 方法,将实例处理程序附加到对象或 XAML 元素。 WPF 路由事件实现使用 AddHandler 方法附加事件处理程序的公共语言运行时 (CLR) 事件包装器。 由于用于附加事件处理程序的 XAML 特性语法会导致调用 CLR 事件包装器,因此即时是在 XAML 中附加处理程序也会解析为 AddHandler 调用。 对于已处理的事件:

  • 不会调用使用 XAML 特性语法或 AddHandler 的公共签名附加的处理程序。
  • 会调用在 handledEventsToo 参数设置为 true 的情况下使用 AddHandler(RoutedEvent, Delegate, Boolean) 重载附加的处理程序。 此重载适用于需要响应已处理的事件的极少数情况。 例如,元素树中的某个元素已将事件标记为已处理,但事件路由中的其他元素需要响应已处理的事件。

下面的 XAML 示例将名为 componentWrapper 的自定义控件(包装名为 componentTextBox 的 TextBox)添加到名为 outerStackPanel 的 StackPanel。 PreviewKeyDown 事件的实例事件处理程序使用 XAML 特性语法附加到 componentWrapper。 因此,实例处理程序只会响应由 componentTextBox 引发的未经处理的 PreviewKeyDown 隧道事件。

<StackPanel Name="outerStackPanel" VerticalAlignment="Center"><custom:ComponentWrapperx:Name="componentWrapper"TextBox.PreviewKeyDown="HandlerInstanceEventInfo"HorizontalAlignment="Center"><TextBox Name="componentTextBox" Width="200" /></custom:ComponentWrapper>
</StackPanel>

MainWindow 构造函数使用 UIElement.AddHandler(RoutedEvent, Delegate, Boolean) 重载(handledEventsToo 参数设置为 true)将 KeyDown 浮升事件的实例处理程序附加到 componentWrapper。 因此,实例事件处理程序会响应未经处理和已处理的事件。

public partial class MainWindow : Window
{public MainWindow(){InitializeComponent();// Attach an instance handler on componentWrapper that will be invoked by handled KeyDown events.componentWrapper.AddHandler(KeyDownEvent, new RoutedEventHandler(Handler.InstanceEventInfo),handledEventsToo: true);}// The handler attached to componentWrapper in XAML.public void HandlerInstanceEventInfo(object sender, KeyEventArgs e) => Handler.InstanceEventInfo(sender, e);
}

下一部分显示了 ComponentWrapper 的代码隐藏实现。

4.2 静态类事件处理程序

可以通过在类的静态构造函数中调用 RegisterClassHandler 方法来附加静态类事件处理程序。 类层次结构中的每个类都可以为每个路由事件注册其自己的静态类处理程序。 因此,可以在事件路由中的任何给定节点上为相同事件调用多个静态类处理程序。 构造事件的事件路由时,每个节点的所有静态类处理程序都会添加到事件路由中。 节点上静态类处理程序的调用顺序从派生程度最高的静态类处理程序开始,接下来是来自每个后续基类的静态类处理程序。

使用 RegisterClassHandler(Type, RoutedEvent, Delegate, Boolean) 重载(handledEventsToo 参数设置为 true)注册的静态类事件处理程序会响应未经处理和已处理的路由事件。

静态类处理程序通常注册为仅响应未经处理的事件。 在这种情况下,如果节点上的派生类处理程序将事件标记为已处理,则不会调用该事件的基类处理程序。 在这种情况下,基类处理程序实际上会被派生类处理程序所替换。 基类处理程序通常在视觉外观、状态逻辑、输入处理和命令处理等领域中帮助控制设计,因此在替换它们时要谨慎。 不将事件标记为已处理的派生类处理程序最终会补充基类处理程序,而不是替换它们。

下面的代码示例演示在前面 XAML 中引用的 ComponentWrapper 自定义控件的类层次结构。 ComponentWrapper 类从 ComponentWrapperBase 派生类,而后者又从 StackPanel 类派生。 在 ComponentWrapper 和 ComponentWrapperBase 类的静态构造函数中使用的 RegisterClassHandler 方法会为其中每个类注册静态类事件处理程序。 WPF 事件系统在 ComponentWrapperBase 静态类处理程序之前调用 ComponentWrapper 静态类处理程序。

public class ComponentWrapper : ComponentWrapperBase
{static ComponentWrapper(){// Class event handler implemented in the static constructor.EventManager.RegisterClassHandler(typeof(ComponentWrapper), KeyDownEvent, new RoutedEventHandler(Handler.ClassEventInfo_Static));}// Class event handler that overrides a base class virtual method.protected override void OnKeyDown(KeyEventArgs e){Handler.ClassEventInfo_Override(this, e);// Call the base OnKeyDown implementation on ComponentWrapperBase.base.OnKeyDown(e);}
}public class ComponentWrapperBase : StackPanel
{// Class event handler implemented in the static constructor.static ComponentWrapperBase(){EventManager.RegisterClassHandler(typeof(ComponentWrapperBase), KeyDownEvent, new RoutedEventHandler(Handler.ClassEventInfoBase_Static));}// Class event handler that overrides a base class virtual method.protected override void OnKeyDown(KeyEventArgs e){Handler.ClassEventInfoBase_Override(this, e);e.Handled = true;Debug.WriteLine("The KeyDown routed event is marked as handled.");// Call the base OnKeyDown implementation on StackPanel.base.OnKeyDown(e);}
}

下一部分会讨论此代码示例中的重写类事件处理程序的代码隐藏实现。

4.3 重写类事件处理程序

某些视觉元素基类会为其每个公共路由输入事件公开空的 <On>事件名称 和 <OnPreview>事件名称 虚拟方法。 例如,UIElement 会实现 OnKeyDown 和 OnPreviewKeyDown 虚拟事件处理程序以及许多其他事件处理程序。 可以重写基类虚拟事件处理程序,以便为派生类实现重写类事件处理程序。 例如,可以通过重写 OnDragEnter 虚拟方法,为任何 UIElement 派生类中的 DragEnter 事件添加重写类处理程序。 重写基类虚拟方法是比在静态构造函数中注册类处理程序更简单的一种实现类处理程序的方法。 在重写中,可以引发事件、启动特定于类的逻辑以更改实例中的元素属性、将事件标记为已处理或执行其他事件处理逻辑。

与静态类事件处理程序不同,WPF 事件系统仅为类层次结构中派生程度最高的类调用重写类事件处理程序。 类层次结构中派生程度最高的类随后可以使用 base 关键字调用虚拟方法的基实现。 在大多数情况下,无论是否将事件标记为已处理,都应调用基本实现。 如果类要求替换基实现(如果有),则应仅省略调用基实现。 在重写代码之前还是之后调用基实现取决于实现的性质。

在前面的代码示例中,基类 OnKeyDown 虚拟方法在 ComponentWrapper 和 ComponentWrapperBase 类中进行重写。 由于 WPF 事件系统仅调用 ComponentWrapper.OnKeyDown 重写类事件处理程序,因此该处理程序使用 base.OnKeyDown(e) 调用 ComponentWrapperBase.OnKeyDown 重写类事件处理程序,后者进而使用 base.OnKeyDown(e) 调用 StackPanel.OnKeyDown 虚拟方法。 前面的代码示例中的事件顺序为:

  1. 附加到 componentWrapper 的实例处理程序由 PreviewKeyDown 路由事件触发。
  2. 附加到 componentWrapper 的静态类处理程序由 KeyDown 路由事件触发。
  3. 附加到 componentWrapperBase 的静态类处理程序由 KeyDown 路由事件触发。
  4. 附加到 componentWrapper 的重写类处理程序由 KeyDown 路由事件触发。
  5. 附加到 componentWrapperBase 的重写类处理程序由 KeyDown 路由事件触发。
  6. KeyDown 路由事件标记为已处理。
  7. 附加到 componentWrapper 的实例处理程序由 KeyDown 路由事件触发。 处理程序已注册(handledEventsToo 参数设置为 true)。

5、复合控件中的输入事件禁止

某些复合控件会在组件级别禁止输入事件
,以便将它们替换为包含更多信息或暗示更特定行为的自定义高级事件。 按照定义,复合控件是由多个实际控件或控件基类组成的。 经典示例是将各种鼠标事件转换为 Click 路由事件的 Button 控件。 Button 基类是间接派生自 UIElement 的 ButtonBase 类。 控制输入处理所需的大部分事件基础结构在 UIElement 级别提供。 UIElement 会公开多个 Mouse 事件,例如 MouseLeftButtonDown 和 MouseRightButtonDown。 UIElement 还实现空虚拟方法 OnMouseLeftButtonDown 和 OnMouseRightButtonDown 作为预注册类处理程序。 ButtonBase 会重写这些类处理程序,在重写处理程序中将 Handled 属性设置 true 为并引发 Click 事件。 大多数侦听器的最终结果是 MouseLeftButtonDown 和 MouseRightButtonDown 事件会隐藏,而高级 Click 事件可见。

解决输入事件禁止问题

有时,各个控件内的事件禁止可能会干扰应用程序中的事件处理逻辑。 例如,如果应用程序使用 XAML 特性语法在 XAML 根元素上附加 MouseLeftButtonDown 事件的处理程序,则不会调用该处理程序,因为 Button 控件将 MouseLeftButtonDown 事件标记为已处理。 如果希望对已处理的路由事件调用应用程序的根的元素,则可以:

  • 通过调用 UIElement.AddHandler(RoutedEvent, Delegate, Boolean) 方法(handledEventsToo 参数设置为 true)来附加处理程序。 此方法需要在获取要附加到的元素的对象引用后,在代码隐藏中附加事件处理程序。

  • 如果标记为已处理的事件是浮升输入事件,则附加配对的预览事件的处理程序(如果可用)。 例如,如果控件禁止了 MouseLeftButtonDown 事件,则可以改为附加 PreviewMouseLeftButtonDown 事件的处理程序。 此方法仅适用于共享事件数据的预览和浮升输入事件对。 请注意不要将 PreviewMouseLeftButtonDown 标记为已处理,因为这会完全禁止 Click 事件。

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

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

相关文章

大创项目推荐 深度学习中文汉字识别

文章目录 0 前言1 数据集合2 网络构建3 模型训练4 模型性能评估5 文字预测6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习中文汉字识别 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xf…

OCP NVME SSD规范解读-4.NVMe IO命令-1

针对NVMe-IO-1到NVMe-IO-14的解读如下&#xff1a; NVMe-IO-1&#xff1a; 设备应支持所有必需的NVMe I/O命令。这是设备能够进行基本数据读写操作的基础要求。NVMe I/O命令包括读、写、删除、擦除等操作&#xff0c;这些是存储设备的核心功能。 NVMe-IO-2&#xff1a; 设备应…

JavaOOP篇----第二十五篇

系列文章目录 文章目录 系列文章目录前言一、一个”.java”源文件中是否可以包含多个类(不是内部类)?有什么限制?二、AnonymousInnerClass(匿名内部类)是否可以继承其它类?是否可以实现接口?三、内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制?四、Java 中…

在 Python 中跳出嵌套循环的 5 种方法

在 Python 中跳出嵌套循环的 5 种方法(5 Ways To Break Out of Nested Loops in Python) 文章目录 在 Python 中跳出嵌套循环的 5 种方法(5 Ways To Break Out of Nested Loops in Python)1. 添加标志变量 Add a Flag Variable2. 抛出异常 Raise an Exception3. 再次检查相同条…

聊聊PowerJob的StoreStrategy

序 本文主要研究一下PowerJob的StoreStrategy StoreStrategy tech/powerjob/worker/common/constants/StoreStrategy.java Getter AllArgsConstructor public enum StoreStrategy {DISK("磁盘"),MEMORY("内存");private final String des; }StoreStra…

建造型设计模式-建造者模式

建造者模式一种对象构建模式&#xff0c;是将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。构建的对象很大并且需要多个步骤时&#xff0c;使用构建器模式&#xff0c;有助于减小构造函数的大小。把一个整体的构造函数分解成各个属性的…

[计算机提升] Windows系统软件:管理类

3.6 系统软件&#xff1a;管理类 3.6.1 运行 通过运行程序&#xff0c;在打开输入框中输入名称&#xff0c;按下回车后可以打开相应的程序、文件夹、文档或Internet资源&#xff1a; 3.6.2 命令提示符&#xff1a;cmd 在Windows系统中&#xff0c;cmd是指"命令提示符…

深入理解 c++ 函数模板

函数模板是C中的一种强大特性&#xff0c;它允许程序员编写一个可以处理多种数据类型的函数。通过使用模板&#xff0c;我们可以编写一次函数&#xff0c;然后在多种数据类型上使用它&#xff0c;这大大提高了代码的复用性。 1. 基本概念 函数模板是一种参数化类型的工具&…

nacos入门篇001-安装与启动

1、下载zip包 我这里下载的是版本2.2.0 Nacos 快速开始 2、修改配置文件 2.1集群模式修改成单例模式 vi startup.sh 2.2 修改数据库配置信息 3、初始化数据库 3.1 创建db名称&#xff1a;db_nacos 3.2 执行mysql-schema.sql 3.3 执行完截图&#xff1a; 4、运行脚本启动 …

听GPT 讲Rust源代码--src/tools(34)

File: rust/src/tools/clippy/clippy_lints/src/collection_is_never_read.rs 文件"collection_is_never_read.rs"位于Rust源代码中的clippy_lints工具中&#xff0c;其作用是检查在集合类型&#xff08;如Vec、HashMap等&#xff09;的实例上执行的操作是否被忽略了…

LT8612UX-HDMI2.0 to HDMI2.0 and VGA Converter with Audio,支持三通道视频DAC

HDMI2.0 to HDMI2.0 and VGA Converter with Audio 1. 描述 LT8612UX是一个HDMI到HDMI和vga转换器&#xff0c;它将HDMI2.0数据流转换为HDMI2.0信号和模拟RGB信号。 它还输出8通道I2S和SPDIF信号&#xff0c;使高质量的7.1通道音频。 LT8612UX支持符合HDMI2.0/ 1.4规范的…

【C#】蜗牛爬井问题C#控制台实现

文章目录 一、问题描述二、C#控制台代码 一、问题描述 井深30米&#xff0c;蜗牛在井底&#xff0c;每天爬3米又滑下1米&#xff0c;问第几天爬出来 二、C#控制台代码 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System…

两个字符串间的最短路径问题 (100%用例)C卷 (JavaPythonNode.jsC语言C++)

给定两个字符串,分别为字符串A与字符串B。例如A字符串为ABCABBA,B字符串为CBABAC可以得到下图m*n的二维数组,定义原点为(0,0),终点为(m,n),水平与垂直的每一条边距离为1,映射成坐标系如下图 从原点(0,0)到(0,A)为水平边,距离为1,从(0,A)到(A,C)为垂直边,距离为1;假设两…

ubuntu python播放MP3,wav音频和录音

目录 一.利用pygame&#xff08;略显麻烦&#xff0c;有时候播放不太正常&#xff09;1.安装依赖库2.代码 二.利用mpg123&#xff08;简洁方便&#xff0c;但仅争对mp3&#xff09;1.安装依赖库2.代码 三.利用sox&#xff08;简单方便&#xff0c;支持的文件格式多&#xff09;…

亚信安慧AntDB数据并行加载工具的实现(一)

1.概述 数据加载速度是评判数据库性能的重要指标&#xff0c;能否提高数据加载速度&#xff0c;对文件数据进行并行解析&#xff0c;直接影响数据库运维管理效率。基于此&#xff0c;AntDB分布式数据库提供了两种数据加载方式&#xff1a; 一是类似于PostgreSQL的Copy命令&am…

2分钟快速了解Redis核心内容

1.简介 Redis 是一种高性能的键值对数据库。 2.详细解释 Redis&#xff08;Remote Dictionary Server&#xff0c;远程字典服务&#xff09;是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。它通…

thingsboard前端缓存--nginx

thingsboardnginx thingsboard部署到阿里云服务器之后&#xff0c;由于登录界面要发送的文件很大&#xff0c;并且服务器的带宽目前有限&#xff0c;因此配置一个nginx&#xff0c;进行前端页面的一些缓存&#xff0c;参考了https://qianchenzhumeng.github.io/posts/Nginx%E5…

C语言编写Windows程序:组合启用/禁用Telnet客户端,并Telnet指定ip和端口

本文程序是将启用/禁用Telnet客户端的命令进行组合&#xff0c;单个命令的解析可参考文章&#xff1a; 启用/禁用Windows功能中的Telnet客户端的命令_()命令将阻止使用telnintel-CSDN博客 源代码如下&#xff1a; #include <stdio.h> #include <stdlib.h> #include…

【重磅新品】小眼睛科技推出紫光同创盘古系列FPGA开发板套件,盘古200K开发板,紫光同创PG2L200H,Logos2系列

FPGA&#xff0c;即现场可编程门阵列&#xff0c;作为可重构电路芯片&#xff0c;已经成为行业“万能芯片”&#xff0c;在通信系统、数字信息处理、视频图像处理、高速接口设计等方面都有不俗的表现。近几年&#xff0c;随着国家战略支持和产业发展&#xff0c;国产FPGA迎来迅…

单机+内部备份_全备案例

此场景为单机数据库节点内部备份&#xff0c;方便部署和操作&#xff0c;但备份REPO与数据库实例处于同一个物理主机&#xff0c;冗余度较低。 前期准备 配置ksql免密登录(必须) 在Kingbase数据库运行维护中&#xff0c;经常用到ksql工具登录数据库&#xff0c;本地免密登录…