前言
本文主要介绍WPF应用对鼠标输入、触摸屏触笔以及触摸事件的封装。
WPF 屏幕点击的设备类型
1、鼠标
可以通过Mouse相关的事件参数MouseButtonEventArgs中的数据,e.StylusDecice==null表示没有触摸设备,所以设备为鼠标
2、触笔 or 触摸
根据StylusDown事件参数StylusDownEventArgs,
e.StylusDevice.TabletDevice.Type == TabletDeviceType.Stylus,True表示触摸设备为触笔,False则为触摸。
注:网上有博客说通过DeviceId来判断是否触笔。这是不对的
e.StylusDevice.Id,触笔的DeviceId在一台电脑上一般是固定的,换台电脑id是会变化的;
触摸的DeviceId则是动态分配变动的。
通过上面设备类型的判断,就可以封装一套设备点击事件,DeviceDown、DeviceUp、DeviceMove。
事件参数中添加设备类型DeviceType(鼠标、触笔、触摸),然后具体业务中可以通过设备类型区分相关的交互操作。
设备输入事件的封装
有个小伙伴问我设备类型具体是如何封装的,那本文就补充下设备输入事件的封装使用,希望给大家提供一点帮助、省去你们磨代码的时间。
除了设备输入类型,输入事件也有多种状态。
简单介绍下事件区分,具体以鼠标事件为例:
private void RegisterMouse()
{
//鼠标-冒泡
if (_element is Button button)
{
//Button类型的冒泡事件,被内部消化处理了,所以需要重新添加路由事件订阅
button.AddHandler(UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(Button_MouseLeftButtonDown), true);
button.AddHandler(UIElement.MouseRightButtonDownEvent, new MouseButtonEventHandler(Button_MouseRightButtonDown), true);
button.AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(Button_MouseLeftButtonUp), true);
button.AddHandler(UIElement.MouseRightButtonUpEvent, new MouseButtonEventHandler(Button_MouseRightButtonUp), true);
}
else
{
_element.MouseLeftButtonDown += Button_MouseLeftButtonDown;
_element.MouseRightButtonDown += Button_MouseRightButtonDown;
_element.MouseLeftButtonUp += Button_MouseLeftButtonUp;
_element.MouseRightButtonUp += Button_MouseRightButtonUp;
}
_element.MouseMove += Element_MouseMove;
//鼠标-隧道事件
_element.PreviewMouseLeftButtonDown += Button_PreviewMouseLeftButtonDown;
_element.PreviewMouseRightButtonDown += Button_PreviewMouseRightButtonDown;
_element.PreviewMouseLeftButtonUp += Button_PreviewMouseLeftButtonUp;
_element.PreviewMouseRightButtonUp += Button_PreviewMouseRightButtonUp;
_element.PreviewMouseMove += Element_PreviewMouseMove;
}
上面区分了按钮与其它的FrameworkElement的鼠标事件,因为Button对冒泡事件是做了拦截再暴露Click事件,需要订阅路由事件来完成鼠标的监听。
如上方代码,对鼠标的左右键、按下抬起、移动以及冒泡隧道都做了完整的封装。
鼠标事件:
private void Element_MouseDown(MouseEventArgs e, int deviceId)
{
if (e.StylusDevice != null) return;
var positionLazy = new Lazy<Point>(() => e.GetPosition(_element));
var deviceInputArgs= new DeviceInputArgs()
{
DeviceId = deviceId,
DeviceType = DeviceType.Mouse,
PositionLazy = positionLazy,
PointsLazy = new Lazy<StylusPointCollection>(() =>
{
var point = positionLazy.Value;
return new StylusPointCollection(new List<StylusPoint>() { new StylusPoint(point.X, point.Y) });
}),
GetPositionFunc = (element, args) => e.GetPosition(element),
SourceArgs = e
};
_deviceDown?.Invoke(_element, deviceInputArgs);
}
触摸面积获取:
/// <summary>
/// 获取含面积的触摸点集合
/// </summary>
/// <param name="stylusEventArgs"></param>
/// <param name="uiElement"></param>
/// <returns></returns>
public static Rect GetTouchPointArea(TouchEventArgs stylusEventArgs, UIElement uiElement)
{
Rect touchArea = Rect.Empty;
var touchPoints = stylusEventArgs.GetIntermediateTouchPoints(uiElement);
foreach (var stylusPoint in touchPoints)
{
var stylusPointArea = stylusPoint.Bounds;
touchArea.Union(stylusPointArea);
}
return touchArea;
}
返回新的触笔输入点集:
/// <summary>
/// 获取触摸点集
/// </summary>
/// <param name="stylusEventArgs"></param>
/// <param name="element"></param>
/// <returns></returns>
private StylusPointCollection GetStylusPoints(StylusEventArgs stylusEventArgs, FrameworkElement element)
{
// 临时去除description
var pointCollection = new StylusPointCollection();
var stylusPointCollection = stylusEventArgs.GetStylusPoints(element);
foreach (var stylusPoint in stylusPointCollection)
{
pointCollection.Add(new StylusPoint(stylusPoint.X, stylusPoint.Y, stylusPoint.PressureFactor));
}
return pointCollection;
}
这里是直接把StylusPoint.Description舍弃,防止在应用层未能统一好这个设备描述、导致异常。
当然这些获取信息根据业务需要来获取、此处设备事件封装不损耗处理延时,所以需要添加Lazy函数如:
PositionLazy = new Lazy< Point>(() => e.GetPosition(_element)), PointsLazy = new Lazy< StylusPointCollection>(() => GetStylusPoints(e, _element)),
暴露的通用设备按下事件,可以看如下定义:
/// <summary>
/// 设备按下
/// </summary>
public event EventHandler<DeviceInputArgs> DeviceDown
{
add => _deviceDown.Add(value, value.Invoke);
remove => _deviceDown.Remove(value);
}
UI控件订阅DeviceDown、DeviceMove、DeviceUp事件,从事件参数DeviceInputArgs获取详细数据:
/// <summary>
/// 设备ID
/// <para>默认为鼠标设备,鼠标左键-1,鼠标右键-2 </para>
/// </summary>
public int DeviceId { get; set; }
/// <summary>
/// 设备类型
/// </summary>
public DeviceType DeviceType { get; set; }
/// <summary>
/// 位置
/// </summary>
public Point Position
{
get => PositionLazy?.Value ?? default;
set => PositionLazy = new Lazy<Point>(() => value);
}
/// <summary>
/// 触笔点集
/// </summary>
public StylusPointCollection Points
{
get => PointsLazy?.Value;
set => PointsLazy = new Lazy<StylusPointCollection>(() => value);
}
/// <summary>
/// 获取相对元素的位置
/// </summary>
public Func<FrameworkElement, Point> GetPosition
{
get => relativeElement => GetPositionFunc(relativeElement, SourceArgs);
set => GetPositionFunc = (relativeElement, args) => value(relativeElement);
}
/// <summary>
/// 事件触发源数据
/// </summary>
public InputEventArgs SourceArgs { get; set; }
/// <summary>
/// 触摸面积
/// </summary>
public Rect TouchArea => TouchAreaLazy?.Value ?? Rect.Empty;
DeviceInputArgs继承Windows路由事件参数类RoutedEventArgs
为何要封装通用事件?
因为两点原因:
1、大部分业务并不需要区分鼠标、触笔、触摸类型
2、有些业务如白板书写、多指操作需要区分鼠标、触笔、触摸类型,这类场景因为操作事件订阅太多,业务逻辑搞复杂了
通过将鼠标、触笔、触摸事件封装为通用输入事件,下游应用业务对设备输入事件处理逻辑就简单化了。
示例源码
Github:github.com/kybs00/Devi…
最后
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。
也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!
优秀是一种习惯,欢迎大家留言学习!
作者:唐宋元明清2188
出处:cnblogs.com/kybs0/p/18325065
声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!