我们都知道路由事件,而附加事件用的比较少。

但如果是通用的场景,类似附加属性,附加事件就很有必要的。

举个例子,输入设备有很多种,WPF中输入事件主要分为鼠标、触摸、触笔:WPF 屏幕点击的设备类型 - 唐宋元明清2188 - 博客园 (cnblogs.com)

有这么多输入事件Mouse、Touch、Stylus,另外按钮Click还处理了冒泡事件,事件类型就更多了。

但绝大部分业务其实并不关心事件类型,只需要一个触发事件就行了。

所以我们有这样的需求:设计并提供一个通用的输入事件,大家只需要拿到事件进行业务操作。另外一些小场景,如果需要具体事件如触摸点集,可以从事件源参数内部去获取。

具体的通用输入事件,我们另外讨论,这里主要描述如何自定义附加事件

附加事件

WPF官方对附加事件的描述 - 附加事件概述 - WPF .NET Framework | Microsoft Learn

所以我们先定义一个附加事件类:

 1     public class DeviceEvents
2 {
3 /// <summary>
4 /// 按压事件
5 /// </summary>
6 public static readonly RoutedEvent PreviewDeviceDownEvent = EventManager.RegisterRoutedEvent("PreviewDeviceDown", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DeviceEvents));
7 public static void AddPreviewDeviceDownHandler(DependencyObject d, RoutedEventHandler handler)
8 {
9 (d as UIElement)?.AddHandler(DeviceEvents.PreviewDeviceDownEvent, handler);
10 }
11 public static void RemovePreviewDeviceDownHandler(DependencyObject d, RoutedEventHandler handler)
12 {
13 (d as UIElement)?.RemoveHandler(DeviceEvents.PreviewDeviceDownEvent, handler);
14 }
15 }

然后,我们看下如何封装原有事件PreviewMouseDown、PreviewStylusDown,并转换成此PreviewDeviceDown事件

在界面上添加下事件:

1     <Button x:Name="TestButton"
2 Content="测试" Height="30" Width="120"
3 local:DeviceEvents.PreviewDeviceDown="TestButton_OnPreviewDeviceDown"/>

我们会发现界面加载时,AddPreviewDeviceDownHandler不会触发调用。附加事件,与附加属性原理不一样,附加事件相当于在编译时注入委托(事件处理程序)AddPreviewDeviceDownHandler,界面加载时不会执行这个委托注册。

所以,我们可以通过另一个附加属性来开关附加事件,并且内部集成事件的触发。具体设计如下:

 1     public class DeviceEvents
2 {
3 private static readonly List<DeviceEventTransformer> EventTransformers = new List<DeviceEventTransformer>();
4
5 /// <summary>
6 /// 是否开启附加事件
7 /// </summary>
8 public static readonly DependencyProperty IsOpenProperty = DependencyProperty.RegisterAttached(
9 "IsOpen", typeof(bool), typeof(DeviceEvents), new PropertyMetadata(default(bool), OnIsOpenPropertyChanged));
10 private static void OnIsOpenPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
11 {
12 if (d is UIElement element && e.NewValue is bool isOpen)
13 {
14 var eventTransformer = EventTransformers.FirstOrDefault(i => i.Target == element);
15 if (isOpen)
16 {
17 if (eventTransformer == null)
18 {
19 eventTransformer = new DeviceEventTransformer(element);
20 EventTransformers.Add(eventTransformer);
21 }
22 eventTransformer.Register();
23 }
24 else
25 {
26 eventTransformer?.UnRegister();
27 }
28 }
29 }
30
31 public static void SetIsOpen(DependencyObject element, bool value)
32 {
33 element.SetValue(IsOpenProperty, value);
34 }
35 public static bool GetIsOpen(DependencyObject element)
36 {
37 return (bool)element.GetValue(IsOpenProperty);
38 }
39
40 /// <summary>
41 /// 按压事件
42 /// </summary>
43 public static readonly RoutedEvent PreviewDeviceDownEvent = EventManager.RegisterRoutedEvent("PreviewDeviceDown", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DeviceEvents));
44 public static void AddPreviewDeviceDownHandler(DependencyObject d, RoutedEventHandler handler)
45 {
46 (d as UIElement)?.AddHandler(DeviceEvents.PreviewDeviceDownEvent, handler);
47 }
48 public static void RemovePreviewDeviceDownHandler(DependencyObject d, RoutedEventHandler handler)
49 {
50 (d as UIElement)?.RemoveHandler(DeviceEvents.PreviewDeviceDownEvent, handler);
51 }
52 }
53 /// <summary>
54 /// 事件转换器
55 /// </summary>
56 public class DeviceEventTransformer
57 {
58 private readonly UIElement _uiElement;
59
60 public DeviceEventTransformer(UIElement uiElement)
61 {
62 _uiElement = uiElement;
63 }
64 public UIElement Target => _uiElement;
65
66 public void Register()
67 {
68 _uiElement.PreviewMouseDown += UiElement_PreviewMouseDown;
69 _uiElement.PreviewStylusDown += UiElement_PreviewStylusDown;
70 }
71
72 public void UnRegister()
73 {
74 _uiElement.PreviewMouseDown -= UiElement_PreviewMouseDown;
75 _uiElement.PreviewStylusDown += UiElement_PreviewStylusDown;
76 }
77
78 private void UiElement_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
79 {
80 OnPreviewDeviceDown(e);
81 }
82 private void UiElement_PreviewStylusDown(object sender, StylusDownEventArgs e)
83 {
84 OnPreviewDeviceDown(e);
85 }
86
87 private void OnPreviewDeviceDown(InputEventArgs e)
88 {
89 RoutedEventArgs reArgs = new RoutedEventArgs(DeviceEvents.PreviewDeviceDownEvent, e);
90 _uiElement.RaiseEvent(reArgs);
91 }
92 }

DeviceEventTransformer是一个事件封装器,如果只是后台代码,也可以直接使用DeviceEventTransformer订阅相关事件。

上面事件实现是一个案例,这里的代码比较粗糙,没有对鼠标、触摸做互斥处理。另外其它的事件类型如冒泡、隧道、Down、Up等,后续可以将DeviceEvents封装完整。

附加事件使用

在界面上,我们可以直接在任意控件上添加此事件:

先添加DeviceEvents.IsOpen="True"开启设备附加事件,
然后订阅相应的附加事件DeviceEvents.PreviewDeviceDown="TestGrid_OnPreviewDeviceDown"

WPF 自定义附加事件的更多相关文章

  1. WPF自定义路由事件(二)

    WPF中的路由事件 as U know,和以前Windows消息事件区别不再多讲,这篇博文中,将首先回顾下WPF内置的路由事件的用法,然后在此基础上自定义一个路由事件. 1.WPF内置路由事件 WPF ...

  2. 细说WPF自定义路由事件

    WPF中的路由事件 as U know,和以前Windows消息事件区别不再多讲,这篇博文中,将首先回顾下WPF内置的路由事件的用法,然后在此基础上自定义一个路由事件. 1.WPF内置路由事件   W ...

  3. WPF:自定义路由事件的实现

    路由事件通过EventManager,RegisterRoutedEvent方法注册,通过AddHandler和RemoveHandler来关联和解除关联的事件处理函数:通过RaiseEvent方法来 ...

  4. WPF自定义RoutedEvent事件示例代码

    ************************* 引用网友,便于查找所用..... 创建自定义路由事件和应用分为6个步骤: (1)自定义路由事件参数对象 (2)声明并注册路由事件 (3)为路由事件添 ...

  5. WPF 自定义路由事件

    如何:创建自定义路由事件 首先自定义事件支持事件路由,需要使用 RegisterRoutedEvent 方法注册 RoutedEvent C#语法 public static RoutedEvent ...

  6. Wpf自定义路由事件

    创建自定义路由事件大体可以分为三个步骤: ①声明并注册路由事件. ②为路由事件添加CLR事件包装. ③创建可以激发路由事件的方法. 以ButtonBase类中代码为例展示这3个步骤: public a ...

  7. WPF自定义路由事件(一)

    首先自定义事件支持事件路由,需要使用 RegisterRoutedEvent 方法注册 RoutedEvent C#语法 public static RoutedEvent RegisterRoute ...

  8. WPF 自定义路由事件 与 附加路由事件

    为student添加附件事件

  9. WPF自定义RoutedEvent事件代码段

    今天在写东西的时候,发现常用的代码段里没有RoutedEvent的,因此,写了一个代码段,方便以后使用,顺便记录一下,如何做代码段. 1.在项目中新建一个XML文件,将扩展名修改为snippet. 2 ...

  10. WPF 之路由事件和附加事件(六)

    一.消息驱动与直接事件模型 ​ 事件的前身是消息(Message).Windows 是消息驱动的系统,运行其上的程序也遵循这个原则.消息的本质就是一条数据,这条消息里面包含着消息的类别,必要的时候还记 ...

随机推荐

  1. Android类加载流程

    背景 由于前前前阵子写了个壳,得去了解类的加载流程,当时记了一些潦草的笔记.这几天把这些东西简单梳理了一下,本文分析的代码基于Android8.1.0源码. 流程分析 从loadClass开始,我们来 ...

  2. 【机器学习】利用 Python 进行数据分析的环境配置 Windows(Jupyter,Matplotlib,Pandas)

    环境配置 安装 python 博主使用的版本是 3.10.6 在 Windows 系统上使用 Virtualenv 搭建虚拟环境 安装 Virtualenv 打开 cmd 输入并执行 pip inst ...

  3. Windows常用快捷键及基本的Dos命令

    Windows 常用快捷键 Ctrl + C: 复制 Ctrl + V: 粘贴 Ctrl + A: 全选 Ctrl + X: 剪贴 Ctrl + Z: 撤销 Ctrl + S: 保存 Alt + F4 ...

  4. 微软出品自动化神器【Playwright+Java】系列(五) 之 常见点击事件操作

    写在前面 明天就是周五了,这周有那么一两天心情特别不好,真的是做什么都没兴致,所以导致整个人都很丧,什么都不想做. 本打算周一就更新这篇文章的,但由于公司一直加班,每天到家很晚,都是挤时间去学,理解后 ...

  5. Linux自动切换用户

    Linux自动切换用户 一.创建sh文件 touch su_user.sh 二.下载脚本 yum install -y expect 三.脚本内容 #!/bin/bash# This is our f ...

  6. 在vue中_this和this的区别

    _this只是一个变量名,this代表父函数,如果在子函数还用this,this的指 向就变成子函数了,_this就是用来存储指向的 普通函数中的this表示调用此函数时的对象,箭头函数里面的this ...

  7. Elasticsearch rest-high-level-client 基本操作

    Elasticsearch rest-high-level-client 基本操作 本篇主要讲解一下 rest-high-level-client 去操作 Elasticsearch , 虽然这个客户 ...

  8. 图学习【参考资料2】-知识补充与node2vec代码注解

    本项目参考: https://aistudio.baidu.com/aistudio/projectdetail/5012408?contributionType=1 *一.正题篇:DeepWalk. ...

  9. Ueditor、FCKeditor、Kindeditor编辑器漏洞

    Ueditor.FCKeditor.Kindeditor编辑器漏洞 免责声明: Ueditor编辑器漏洞 文件上传漏洞 XSS漏洞 SSRF漏洞 FCKeditor编辑器漏洞 查看FCKeditor版 ...

  10. 棋盘覆盖(java实现)

    棋盘覆盖 问题描述 在一个2k×2k 个方格组成的棋盘中,恰有一个方格与其它方格不同,称该方格为一特殊方格,且称该棋盘为一特殊棋盘.在棋盘覆盖问题中,要用图示的4种不同形态的L型骨牌覆盖给定的特殊棋盘 ...