本文主要介绍WPF应用对鼠标输入、触摸屏触笔以及触摸事件的封装

之前有简单说明设备输入类型 WPF 屏幕点击的设备类型 - 唐宋元明清2188 - 博客园 (cnblogs.com)

1、鼠标 - 通过Mouse相关的事件参数MouseButtonEventArgs中的数据,e.StylusDecice==null表示没有触摸设备,所以设备为鼠标

2、触笔 or 触摸 - 根据StylusDown事件参数StylusDownEventArgs,e.StylusDevice.TabletDevice.Type == TabletDeviceType.Stylus,True表示触摸设备为触笔,False则为触摸。

通过上面设备类型的判断,就可以封装一套设备点击事件,DeviceDown、DeviceUp、DeviceMove。事件参数中添加设备类型DeviceType(鼠标、触笔、触摸),然后具体业务中可以通过设备类型区分相关的交互操作。

博客园有个小伙伴问我设备类型具体是如何封装的,那本文就补充下设备输入事件的封装使用,希望给大家提供一点帮助、省去你们磨代码的时间。

除了设备输入类型,输入事件也有多种状态。简单介绍下事件区分,具体以鼠标事件为例:

 1     private void RegisterMouse()
2 {
3 //鼠标-冒泡
4 if (_element is Button button)
5 {
6 //Button类型的冒泡事件,被内部消化处理了,所以需要重新添加路由事件订阅
7 button.AddHandler(UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(Button_MouseLeftButtonDown), true);
8 button.AddHandler(UIElement.MouseRightButtonDownEvent, new MouseButtonEventHandler(Button_MouseRightButtonDown), true);
9 button.AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(Button_MouseLeftButtonUp), true);
10 button.AddHandler(UIElement.MouseRightButtonUpEvent, new MouseButtonEventHandler(Button_MouseRightButtonUp), true);
11 }
12 else
13 {
14 _element.MouseLeftButtonDown += Button_MouseLeftButtonDown;
15 _element.MouseRightButtonDown += Button_MouseRightButtonDown;
16 _element.MouseLeftButtonUp += Button_MouseLeftButtonUp;
17 _element.MouseRightButtonUp += Button_MouseRightButtonUp;
18 }
19 _element.MouseMove += Element_MouseMove;
20
21 //鼠标-隧道事件
22 _element.PreviewMouseLeftButtonDown += Button_PreviewMouseLeftButtonDown;
23 _element.PreviewMouseRightButtonDown += Button_PreviewMouseRightButtonDown;
24 _element.PreviewMouseLeftButtonUp += Button_PreviewMouseLeftButtonUp;
25 _element.PreviewMouseRightButtonUp += Button_PreviewMouseRightButtonUp;
26 _element.PreviewMouseMove += Element_PreviewMouseMove;
27 }

上面区分了按钮与其它的FrameworkElement的鼠标事件,因为Button对冒泡事件是做了拦截再暴露Click事件,需要订阅路由事件来完成鼠标的监听。

如上方代码,对鼠标的左右键、按下抬起、移动以及冒泡隧道都做了完整的封装。

鼠标事件:

 1     private void Element_MouseDown(MouseEventArgs e, int deviceId)
2 {
3 if (e.StylusDevice != null) return;
4 var positionLazy = new Lazy<Point>(() => e.GetPosition(_element));
5 var deviceInputArgs= new DeviceInputArgs()
6 {
7 DeviceId = deviceId,
8 DeviceType = DeviceType.Mouse,
9 PositionLazy = positionLazy,
10 PointsLazy = new Lazy<StylusPointCollection>(() =>
11 {
12 var point = positionLazy.Value;
13 return new StylusPointCollection(new List<StylusPoint>() { new StylusPoint(point.X, point.Y) });
14 }),
15 GetPositionFunc = (element, args) => e.GetPosition(element),
16 SourceArgs = e
17 };
18 _deviceDown?.Invoke(_element, deviceInputArgs);
19 }

触摸面积获取:

 1     /// <summary>
2 /// 获取含面积的触摸点集合
3 /// </summary>
4 /// <param name="stylusEventArgs"></param>
5 /// <param name="uiElement"></param>
6 /// <returns></returns>
7 public static Rect GetTouchPointArea(TouchEventArgs stylusEventArgs, UIElement uiElement)
8 {
9 Rect touchArea = Rect.Empty;
10 var touchPoints = stylusEventArgs.GetIntermediateTouchPoints(uiElement);
11 foreach (var stylusPoint in touchPoints)
12 {
13 var stylusPointArea = stylusPoint.Bounds;
14 touchArea.Union(stylusPointArea);
15 }
16 return touchArea;
17 }

返回新的触笔输入点集:

 1     /// <summary>
2 /// 获取触摸点集
3 /// </summary>
4 /// <param name="stylusEventArgs"></param>
5 /// <param name="element"></param>
6 /// <returns></returns>
7 private StylusPointCollection GetStylusPoints(StylusEventArgs stylusEventArgs, FrameworkElement element)
8 {
9 // 临时去除description
10 var pointCollection = new StylusPointCollection();
11 var stylusPointCollection = stylusEventArgs.GetStylusPoints(element);
12 foreach (var stylusPoint in stylusPointCollection)
13 {
14 pointCollection.Add(new StylusPoint(stylusPoint.X, stylusPoint.Y, stylusPoint.PressureFactor));
15 }
16 return pointCollection;
17 }

这里是直接把StylusPoint.Description舍弃,防止在应用层未能统一好这个设备描述、导致异常

当然这些获取信息根据业务需要来获取、此处设备事件封装不损耗处理延时,所以需要添加Lazy函数如:

PositionLazy = new Lazy<Point>(() => e.GetPosition(_element)),
PointsLazy = new Lazy<StylusPointCollection>(() => GetStylusPoints(e, _element)),

暴露的通用设备按下事件,可以看如下定义:

1     /// <summary>
2 /// 设备按下
3 /// </summary>
4 public event EventHandler<DeviceInputArgs> DeviceDown
5 {
6 add => _deviceDown.Add(value, value.Invoke);
7 remove => _deviceDown.Remove(value);
8 }

UI控件订阅DeviceDown、DeviceMove、DeviceUp事件,从事件参数DeviceInputArgs获取详细数据:

 1         /// <summary>
2 /// 设备ID
3 /// <para>默认为鼠标设备,鼠标左键-1,鼠标右键-2 </para>
4 /// </summary>
5 public int DeviceId { get; set; }
6 /// <summary>
7 /// 设备类型
8 /// </summary>
9 public DeviceType DeviceType { get; set; }
10
11 /// <summary>
12 /// 位置
13 /// </summary>
14 public Point Position
15 {
16 get => PositionLazy?.Value ?? default;
17 set => PositionLazy = new Lazy<Point>(() => value);
18 }
19
20 /// <summary>
21 /// 触笔点集
22 /// </summary>
23 public StylusPointCollection Points
24 {
25 get => PointsLazy?.Value;
26 set => PointsLazy = new Lazy<StylusPointCollection>(() => value);
27 }
28
29 /// <summary>
30 /// 获取相对元素的位置
31 /// </summary>
32 public Func<FrameworkElement, Point> GetPosition
33 {
34 get => relativeElement => GetPositionFunc(relativeElement, SourceArgs);
35 set => GetPositionFunc = (relativeElement, args) => value(relativeElement);
36 }
37
38 /// <summary>
39 /// 事件触发源数据
40 /// </summary>
41 public InputEventArgs SourceArgs { get; set; }
42
43 /// <summary>
44 /// 触摸面积
45 /// </summary>
46 public Rect TouchArea => TouchAreaLazy?.Value ?? Rect.Empty;

DeviceInputArgs继承Windows路由事件参数类RoutedEventArgs

为何要封装通用事件?因为2点原因:

1. 大部分业务并不需要区分鼠标、触笔、触摸类型

2. 有些业务如白板书写、多指操作需要区分鼠标、触笔、触摸类型,这类场景因为操作事件订阅太多,业务逻辑搞复杂了

通过将鼠标、触笔、触摸事件封装为通用输入事件,下游应用业务对设备输入事件处理逻辑就简单化了。

封装Demo可详见:Kybs0/DeviceInputTransformDemo: 触摸屏事件转换处理Demo (github.com)

关键字:鼠标、触笔、触摸

WPF 设备输入事件封装的更多相关文章

  1. WPF 自定义附加事件

    我们都知道路由事件,而附加事件用的比较少. 但如果是通用的场景,类似附加属性,附加事件就很有必要的. 举个例子,输入设备有很多种,WPF中输入事件主要分为鼠标.触摸.触笔:WPF 屏幕点击的设备类型 ...

  2. 《WPF程序设计指南》读书笔记——第9章 路由输入事件

    1.使用路由事件 路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件.通俗地说,路由事件会在可视树(逻辑树是其子集)上,上下routed,如果哪个节点上订阅了 ...

  3. WPF 触摸到事件

    原文:WPF 触摸到事件 本文从代码底层告诉大家,在触摸屏幕之后是如何拿到触摸点并且转换为事件 在 WPF 界面框架核心就是交互和渲染,触摸是交互的一部分.在 WPF 是需要使用多个线程来做触摸和渲染 ...

  4. WPF - 善用路由事件

    原文:WPF - 善用路由事件 在原来的公司中,编写自定义控件是常常遇到的任务.但这些控件常常拥有一个不怎么好的特点:无论是内部还是外部都没有使用路由事件.那我们应该怎样宰自定义控件开发中使用路由事件 ...

  5. Android系统输入事件分发详解

    什么是输入事件? 我们知道,运行android系统的设备本质上是一台计算机,使用者在和计算机进行交互的时候可以抽象成简单的对计算机的输入和输出(IO).那么对于运行在计算机上的操作系统来说,操作系统在 ...

  6. Android输入事件详解

    输入事件 在 Android 系统中,从用户与应用的交互中截获事件的方法不止一种.如考虑截获用户界面内的事件,则可从用户与之交互的特定视图对象中捕获事件. 为此,View 类提供了多种方法. 在您将用 ...

  7. Chromium网页输入事件捕捉和手势检測过程分析

    连续的输入事件可能会产生一定的手势操作.比如滑动手势和捏合手势. 在Chromium中,网页的输入事件是在Browser进程中捕捉的.Browser进程捕获输入事件之后,会进行手势操作检測.检測出来的 ...

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

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

  9. WPF的路由事件、冒泡事件、隧道事件(预览事件)

    本文摘要: 1:什么是路由事件: 2:中断事件路由: 3:自定义路由事件: 4:为什么需要自定义路由事件: 5:什么是冒泡事件和预览事件(隧道事件): 1:什么是路由事件 WPF中的事件为路由事件,所 ...

  10. javascript通用事件封装

    随着最近几年Html5的兴起,越来越多的应用采用html5进行实现,一个优秀的网页应用不但需要美观简洁的UI界面,更需要一个良好的交互.网页应用大部分的交互需要用javascript事件进行实现.虽然 ...

随机推荐

  1. 嵌入式工程师进阶,基于AM64x开发板的IPC多核开发案例分享

    前 言 本文档主要说明AM64x基于IPC的多核开发方法.默认使用AM6442进行测试演示,AM6412测试步骤与之类似. 适用开发环境如下: Windows开发环境:Windows 7 64bit. ...

  2. ElasticSearch不区分字母大小写搜索

    0.停止使用该索引的服务(避免新加了数据没备份) 1.备份filesearch索引(检查备份的索引和原索引数据条数是否一致) 1 POST http://127.0.0.1:9200/_reindex ...

  3. 韦东山IMX6ULL Linux开发板基于Buildroot系统QT应用环境配置开发运行

    @ 目录 一. 编译系统 1.设置交叉编译工具链 2.编译系统 二. QT下载 1.安装 Qtcreator 2.创建第一个程序 3.配置 QtCreator 开发环境 4.移植QT程序到开发板 一. ...

  4. 松灵机器人scout mini小车 自主导航(2)——仿真指南

    松灵机器人Scout mini小车仿真指南 之前介绍了如何通过CAN TO USB串口实现用键盘控制小车移动.但是一直用小车测试缺乏安全性.而松灵官方贴心的为我们准备了gazebo仿真环境,提供了完整 ...

  5. Nginx 可视化配置神器NginxConfig

    Nginx 是前后端开发工程师必须掌握的神器.该神器有很多使用场景:比如反向代理.负载均衡.动静分离.跨域等等. 把 Nginx 下载下来打开 conf 文件夹的 nginx.conf 文件,Ngin ...

  6. 要想业务中台建得快,最好用Service Mesh来带

    中国企业数字化转型进入深水区,业务中台及下一代微服务Service Mesh(服务网格)被越来越多的人关注,本文结合网易轻舟微服务Service Mesh实践,解析业务中台为什么需要Service M ...

  7. 使用C#/.NET解析Wiki百科数据实现获取历史上的今天

    创建一个webapi项目做测试使用.   创建新控制器,搭建一个基础框架,包括获取当天日期.wiki的请求地址等 创建一个Http请求帮助类以及方法,用于获取指定URL的信息   使用http请求访问 ...

  8. oeasy教您玩转linux010204-figlet

    我们来回顾一下 上一部分我们都讲了什么? 用 apt 查询并下载了 linuxlogo 用字符画出了 linux 发行版的 logo 还查了手册,通过改参数控制输出信息 我们还能玩点什么呢? 这个实验 ...

  9. python 列表append和 的区别??

    python列表中的合并 python列表中append方法是给列表增加一个元素,而'+='是增加上该符号后边的元素,类似于extend方法 不知道对错,先记下来.我学的append方法是在列表最后追 ...

  10. Jmeter参数化1-随机数设置

    背景:当新增接口的某个字段是唯一性,每次调用该新增接口都会需要单独传入这个字段,麻烦且繁琐. 解决:jmeter设置随机数参数,然后接口调用该参数就达到了自动性不再需要人工传入不同的值.方便调用接口, ...