首页 > 其他分享 >WPF 设备输入事件封装

WPF 设备输入事件封装

时间:2024-08-22 17:16:07浏览次数:7  
标签:封装 鼠标 触摸 Button element 事件 new WPF 输入

本文主要介绍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可详见:kybs00/DeviceInputEventDemo (github.com)

 

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

标签:封装,鼠标,触摸,Button,element,事件,new,WPF,输入
From: https://www.cnblogs.com/kybs0/p/18325065

相关文章

  • el-tree封装。可以搜索/下拉/高亮/能操作增删改查
    项目场景:     el-tree树形图组写成一个组件,并控制默认高亮问题描述     el-tree树形图组写成一个组件,并控制默认高亮。上边存在搜索框和下拉框。能添加和删除解决方案:组件代码:<template><divclass="grid-contentbg-purple"><!--标题--><......
  • WPF中如何使用后台代码动态创建数据模板(DataTemplate)
    数据模板回顾 在WPF中数据模板可以控制数据的呈现方式。对于一些简单的数据,例如一个string,一个int,在显示时,无须额外控制。但是对于复杂数据类型,就需要使用数据模板来控制数据的呈现方式。 一个很简单的例子假设我们定义了一个学生类1publicclassStudent2......
  • Python开发中,SQLAlchemy 的同步操作和异步操作封装,以及常规CRUD的处理。
    在我们使用Python来和数据库打交道中,SQLAlchemy是一个非常不错的ORM工具,通过它我们可以很好的实现多种数据库的统一模型接入,而且它提供了非常多的特性,通过结合不同的数据库驱动,我们可以实现同步或者异步的处理封装。1、SQLAlchemy介绍SQLAlchemy 是一个功能强大且灵活的Python......
  • 触摸输入故障深度剖析:tiptsf.dll修复的高级策略详解
    针对tiptsf.dll文件丢失或损坏导致的触摸输入问题,可以采取以下专业修复步骤:1.安全模式启动:首先,尝试重启计算机进入安全模式。这有助于防止加载可能干扰修复过程的第三方服务。2.系统还原点:检查是否有最近的系统还原点。通过“控制面板”->“系统”->“系统保护”->......
  • 编写类A03,实现数组的复制功能copyArr,输入旧数组,返回一个新数组,元素和旧数组一样
    1publicclassHomework04{2//编写一个main方法3publicstaticvoidmain(String[]args){45int[]oldArr={10,30,50};6A03a03=newA03();7int[]newArr=a03.copyArr(oldArr);8//遍历newArr,验证9......
  • 「对比评测」标准WPF DataGrid与DevExpress WPF GridControl有何不同?(一)
    DevExpressWPF拥有120+个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpressWPF能创建有着强大互动功能的XAML基础应用程序,这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。无论是Office办公软件的衍伸产品,还是以数据为中心......
  • 【python】面向对象之封装
    一:面向对象编程有三大特性:封装、继承、多态二:其中最重要的一个特性就是封装。封装指的就是把数据与功能都整合到一起三:什么是封装?1.数据角度讲,将一些基础数据类型复合成一个自定义类型2.行为角度讲,向类外提供必要的功能,隐藏实现的细节3.设计角度分而治之将一个......
  • WPF:MVVM的由来与属性绑定的过程
    WPF:MVVM的由来与属性绑定的过程1、MVVM(1)MVVM是什么?​ MVVM(Model-View-ViewModel)是一种软件架构设计模式MVVM模式。有助于分离应用程序的业务逻辑和用户界面层,使得开发过程更易于管理,同时也便于单元测试。Model?现实世界中对象的抽象结果。View?View=UI。ViewModel?Vie......
  • 华为OD机试 - 密码输入检测 字符串
    只在本地跑过,不确保一定对#include<stdio.h>#include<stdlib.h>#include<stdbool.h>#include<string.h>#definesetbit(value,bit)(value=value|(1<<bit))intmain(void){charbuf[201];charout[201];inti=0;int......
  • HTN7862 4V~65V输入,2.8A异步降压变换器
    1、特点 2.8A降压,内置260mΩ功率管输入电压范围:4V~65V脉冲跳跃模式使得轻载下高效率·110uA静态电流最高2MHZ可编程开关频率峰值电流控制架构欠压保护、过流保护和过热关断保护无铅封装,ESOP82、应用·12V,24V,48V工业和电信电源轨系统·汽车系统分布式电源系统高......