2019-5-13-WPF-从触摸消息转触摸事件
| title | author | date | CreateTime | categories |
|---|---|---|---|---|
|
WPF 从触摸消息转触摸事件
|
lindexi
|
2019-05-13 09:43:48 +0800
|
2019-05-12 15:12:31 +0800
|
WPF
|
在 WPF 程序可能因为一些坑让程序触摸失效,如果此时还可以收到系统的触摸消息,那么可以通过从触摸消息转触摸事件解决程序触摸失效但不适合所有触摸失效程序
在 WPF 的触摸代码写的不是很清真,特别是触摸到事件可能出现一些坑,如WPF 在触摸线程等待主线程窗口关闭会让主线程和触摸线程相互等待 和 WPF 插拔触摸设备触摸失效 等,有时候在开机的过程,如果启动快了,触摸设备还没准备好,刚好在 WPF 初始化的过程 USB 触摸设备才准备好,此时 WPF 也会触摸失效
在希沃的设备通过判断用户的开机启动时间,如果启动时间过短,那么就需要多判断是不是 USB 设备还没准备好,如果 USB 还没准备好,那么通过一些黑科技告诉用户重新启动。因为在希沃的设备上主要是触摸屏幕,用户不会有鼠标,如果出现了初始化的过程刚好就是 USB 准备好,那么这个程序将收不到任何触摸事件
在程序启动的时候,可以通过获得触摸精度和触摸点判断当前是否存在触摸设备,如果不存在触摸设备同时判断是在希沃的设备上运行,那么就是触摸失效了。但是还可以收到系统的触摸消息,可以通过本文的黑科技收到触摸
在 WPF 的框架,触摸是从 PENIMC 里面获取的,如果通过自己创建一个模拟的触摸设备,请看 WPF 模拟触摸设备 也可以做到模拟一个触摸。在默认的 WPF 程序是收不到系统的触摸消息,需要禁用实时触摸才可以收到触摸消息,在 Win7 和之后都可以从系统收到 WM_TOUCH 消息,通过这个消息可以解析当前的触摸点和触摸面积,通过这两个值可以用来模拟触摸走原有的 WPF 触摸
在使用 WM_TOUCH 消息需要用到一些本地的方法,先定义一个 NativeMethods 类,用来放本地方法
internal static class NativeMethods
{
public const int WM_TOUCH = 0x0240;
public const uint TWF_WANTPALM = 0x00000002; [DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool RegisterTouchWindow(IntPtr hWnd, uint ulFlags); [DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetTouchInputInfo(IntPtr hTouchInput, int cInputs,
[In, Out] TOUCHINPUT[] pInputs, int cbSize); [DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern void CloseTouchInputHandle(IntPtr lParam);
}
因为这个类的定义方法比较多,所以就不在本文告诉大家,请看源代码
在开启触摸消息之前需要在 Window 的 SourceInitialized 事件触发之后才能调用
创建 MessageTouchDevice 继承 TouchDevice 从 WPF 模拟触摸设备 可以知道这个类可以用来模拟触摸,在这个类添加一个静态的方法 UseMessageTouch 用它传入窗口
public MainWindow()
{
InitializeComponent(); SourceInitialized += MainWindow_SourceInitialized;
} private void MainWindow_SourceInitialized(object sender, EventArgs e)
{
MessageTouchDevice.UseMessageTouch(this);
}
在 UseMessageTouch 方法需要先通过禁用实时触摸然后使用钩子拿到消息
/// <summary>
/// 使用消息触摸
/// 注意 开启了消息触摸之后,原有的 WPF 触摸将会无法再次使用
/// </summary>
public static void UseMessageTouch(Window window)
{
// 先禁用 WPF 触摸
TabletHelper.DisableWPFTabletSupport(hWnd); NativeMethods.RegisterTouchWindow(hWnd, NativeMethods.TWF_WANTPALM);
HwndSource source = HwndSource.FromHwnd(hWnd); source.AddHook((IntPtr hwnd, int msg, IntPtr param, IntPtr lParam, ref bool handled) =>
{
WndProc(window, msg, param, lParam, ref handled);
return IntPtr.Zero;
});
}
定义 WndProc 静态方法用来收到消息,通过消息 msg 可以判断当前是否触摸消息,然后通过 wParam 计算出当前的触摸收集到的次数
因为 Windows 消息触发比较慢,也就是没有 PENIMC 拿到触摸点那么快,在一次触发的时候可以拿到多个触摸输入
private static void WndProc(Window window, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == NativeMethods.WM_TOUCH)
{
}
}
通过下面代码,可以找到当前的消息有多少次输入
var inputCount = wParam.ToInt32() & 0xffff;
然后创建一个数组,从 GetTouchInputInfo 获取所有的输入
var inputs = new NativeMethods.TOUCHINPUT[inputCount];
NativeMethods.GetTouchInputInfo(lParam, inputCount, inputs, NativeMethods.TouchInputSize);
如果可以拿到输入,那么 GetTouchInputInfo 将会返回 true 通过这个判断
然后遍历 inputs 输入进行转换事件,从 WPF 模拟触摸设备 找到通过封装的 Down 等方法可以转换为事件,请看代码
在 GetTouchInputInfo 方法拿到的输入的类包含了当前触摸的屏幕坐标和触摸的面积,拿到的数据其实是原有是的百分之一也就是需要除以100才是像素
[StructLayout(LayoutKind.Sequential)]
internal struct TOUCHINPUT
{
/// <summary>
/// 触控输入的 X 坐标(水平点)。此成员用物理屏幕坐标的像素的百分之一表示
/// </summary>
public int X; /// <summary>
/// 触控输入的 y 坐标(垂直点)。此成员用物理屏幕坐标的像素的百分之一表示
/// </summary>
public int Y; /// <summary>
/// 源输入设备的设备句柄。触控输入提供程序在运行时为每个设备指定一个唯一的提供程序
/// </summary>
public IntPtr Source; /// <summary>
/// 一个用于区别某个特定触控输入的触控点标识符。此值在触控点序列中从触控点下降到重新上升的整个过程中保持一致。稍后可对后续触控点重用一个 ID
/// </summary>
public int DwID; /// <summary>
/// 用于指定触控点按住、释放和移动的各个方面
/// </summary>
public TOUCHEVENTF DwFlags; /// <summary>
/// 指定结构中包含有效值的可选字段。可选字段中的有效信息的可用性是特定于设备的
/// </summary>
public TOUCHINPUTMASK DwMask; /// <summary>
/// 事件的时间戳(以毫秒为单位)。使用方应用程序应通知系统不对此字段进行验证
/// </summary>
public int DwTime; public IntPtr DwExtraInfo; /// <summary>
/// 触控区域的宽度用物理屏幕坐标的像素的百分之一表示。只有在 <see cref="DwMask"/> 成员设置了 TOUCHEVENTFMASK_CONTACTAREA 标记的情况下,此值才会有效
/// </summary>
public int CxContact; /// <summary>
/// 触控区域的高度用物理屏幕坐标的像素的百分之一表示。只有在 <see cref="DwMask"/> 成员设置了 TOUCHEVENTFMASK_CONTACTAREA 标记的情况下,此值才会有效
/// </summary>
public int CyContact;
}
通过下面代码可以将 TOUCHINPUT 转换为屏幕坐标和触摸面积,注意这里没有处理任何 DPI 相关,也就是我认为当前的屏幕是 96 的 DPI 的时候下面的转换的就是相对屏幕的坐标
var position = new Point(input.X / 100.0, input.Y / 100.0);
var size = new Size(input.CxContact / 100.0, input.CyContact / 100.0);
在一次触摸的过程,需要使用相同的 TouchDevice 于是在按下和移动等需要有一个相同的实例,通过创建一个静态的字典按照触摸的 id 存放
private static readonly Dictionary<int, MessageTouchDevice>
_devices = new Dictionary<int, MessageTouchDevice>();
在判断没有存在设备的时候创建
if (!_devices.TryGetValue(input.DwID, out var device))
{
device = new MessageTouchDevice(input.DwID, window);
_devices.Add(input.DwID, device);
}
在判断是按下的时候触发按下
if (!device.IsActive && input.DwFlags.HasFlag(NativeMethods.TOUCHEVENTF.TOUCHEVENTF_DOWN))
{
device.Position = position;
device.Size = size;
device.Down();
}
其他事件也差不多,另外在 GetTouchPoint 方法需要做一点修改,添加属性 Position 和 Size 在获取的时候返回
/// <summary>
/// 触摸点
/// </summary>
private Point Position { set; get; } /// <summary>
/// 触摸大小
/// </summary>
private Size Size { set; get; } public override TouchPoint GetTouchPoint(IInputElement relativeTo)
{
return new TouchPoint(this, Position, new Rect(Position, Size), TouchAction);
}
上面代码没有按照约定,返回输入元素相对的坐标,而是返回屏幕的坐标,所以请小伙伴自己修改代码才能在项目使用,同时因为使用的是屏幕的坐标,所以在主窗口触摸的时候,如果判断当前的触摸点在屏幕之外,那么就不会触发主窗口的触摸。因为主窗口期望的是返回的输入的点是相对的主窗口的坐标而不是相对于屏幕的坐标
所有代码放在 github 欢迎小伙伴帮忙修改
除了通过 Touch 消息之外,在 Win7 以上的系统,如 Window 10 系统支持 Pointer 消息,可以通过 把触摸提升 Pointer 消息 将触摸消息转 Pointer 消息进行模拟
2019-5-13-WPF-从触摸消息转触摸事件的更多相关文章
- 2019-11-29-WPF-从触摸消息转触摸事件
原文:2019-11-29-WPF-从触摸消息转触摸事件 title author date CreateTime categories WPF 从触摸消息转触摸事件 lindexi 2019-11- ...
- 通过 AppSwitch 禁用 WPF 内置的触摸让 WPF 程序可以处理 Windows 触摸消息
原文:通过 AppSwitch 禁用 WPF 内置的触摸让 WPF 程序可以处理 Windows 触摸消息 WPF 框架自己实现了一套触摸机制,但同一窗口只能支持一套触摸机制,于是这会禁用系统的触摸消 ...
- 2dx关于js响应layer触摸消息的bug
cocos2dx关于js响应layer触摸消息的bug cocos2d-x 3.7 问题描述: 目前这个版本中(3.7),c++层的layer触摸消息只能通过消息的方式发送给js,不能像lua一样直接 ...
- WPF处理Windows消息
WPF中处理消息首先要获取窗口句柄,创建HwndSource对象 通过HwndSource对象添加消息处理回调函数. HwndSource类: 实现其自己的窗口过程. 创建窗口之后使用 AddHook ...
- 第13讲- Android之消息提示Notification
第13讲 Android之消息提示Notification .Notification Notification可以理解为通知的意思一般用来显示广播信息,通知可以显示到系统的上方的状态栏(status ...
- 每日一练ACM 2019.04.13
2019.04.13 第1002题:A+B Proble Ⅱ Problem DescriptionI have a very simple problem for you. Given two in ...
- Windows 消息循环(2) - WPF中的消息循环
接上文: Windows 消息循环(1) - 概览 win32/MFC/WinForm/WPF 都依靠消息循环驱动,让程序跑起来. 本文介绍 WPF 中是如何使用消息循环来驱动程序的. 4 消息循环在 ...
- WPF 获得触摸精度和触摸点
原文:WPF 获得触摸精度和触摸点 本文主要告诉大家如何获得所有的触摸设备的触摸精度和触摸点数. 需要通过反射的方法才可以拿到触摸的精度. 使用 Tablet.TabletDevices 可以获得所有 ...
- C# WPF QQ新消息托盘悬浮窗效果实现
原文:C# WPF QQ新消息托盘悬浮窗效果实现 今天在做一个项目的时候需要这么一个效果,但是网上找了一会发现并没有现成的给我参考(复制),但是呢,我千(到)辛(处)万(抄)苦(袭)想(复)破(制)头 ...
随机推荐
- vue.js axios实现跨域http请求接口
跨域post实例,用到了qs组件来避开ajax信使请求,并兼容Android. import axios from 'axios'; import qs from 'qs'; axios.post(' ...
- [转]WPF更换主题
如果要做到一个应用程序其基本的内容不变,但改变整个应用程序的外观可以这样做: 对于每一套外观定义一个ResourceDictionary 在应用程序中,动态加载此应用程序(或窗体)的Resource ...
- svn 设置快捷命令
# some more svn aliases alias svnset='svn propset svn:externals . -F' alias svnget='svn propget svn: ...
- Error-SQLServer:【失败】win7装SQL server2017
ylbtech-Error-SQLServer:[失败]win7装SQL server2017 1.返回顶部 1. 2018年08月15日 22:06:38 USCWIFI 阅读数:5433 版 ...
- MyBatis配置文件(七)--environments运行环境
一.environments配置信息: environments的作用是用来配置数据库信息,可以配置多个,其有两个可配的子元素,分别是:事务管理器transactionManager和数据源dataS ...
- 视频透雾原理加视频增强Retinex算法介绍
(本文转自:http://www.syphong.cn/52-1.html#) 视频透雾原理加视频增强Retinex算法介绍 -上海凯视力成 钟建军 一. 视频增强的背景 视觉信息是人类获得外界信息的 ...
- 二叉树遍历问题、时间空间复杂度、淘汰策略算法、lru数据结构、动态规划贪心算法
二叉树的前序遍历.中序遍历.后序遍历 前序遍历 遍历顺序规则为[根左右] ABCDEFGHK 中序遍历 遍历顺序规则为[左根右] BDCAEHGKF 后序遍历 遍历顺序规则为[左右根] DCBHKGF ...
- react-native start停止在Loading dependency graph, done.
在试验的过程中. 发现运行 react-native start会卡住,停留在Loading dependency graph, done. 原因大概是之前运行过 react-native run-a ...
- Laravel Carbon获取 某个时间后N个月的时间
$time = "2020-11-20 00:00:00"; $res = (new Carbon)->setTimeFromTimeString($time)->ad ...
- 配置android studio环境2
安装android studio 2.1运行 exe 程序 安装截图 备注 :O(∩_∩)O~等了 ,但是还是失败, 完全安装啊,不影响,可以手动运行安装目录下的 如:D:\Program Files ...