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_02_vue.js的基础指令
1.v-cloak 作用:解决插值表达式闪烁的问题 当网速过慢时,或者加载数据时间过长时,网页会出现 {{msg}} 的现象 使用方法: <!--缺陷需要写style样式--> < ...
- HTML5 drag拖动事件
参考链接:https://segmentfault.com/a/1190000013606983 例子: <!DOCTYPE HTML> <html> <head> ...
- 创建一个欢迎 cookie 利用用户在提示框中输入的数据创建一个 JavaScript Cookie,当该用户再次访问该页面时,根据 cookie 中的信息发出欢迎信息。
创建一个欢迎 cookie 利用用户在提示框中输入的数据创建一个 JavaScript Cookie,当该用户再次访问该页面时,根据 cookie 中的信息发出欢迎信息. <html> & ...
- idea使用及其快捷键(Jetbrains很多是通用的)(转)
Java程序员肯定会使用idea进行开发,因为其非常强大,很好用,而且可以很傻瓜式导入gradle,用来做SSM项目也很简单 学生是可以使用教育邮箱或者上床学生证使用免费的jetbrains全家桶的, ...
- 【Streaming】Storm内部通信机制分析
一.任务执行及通信的单元 Storm中关于任务执行及通信的三个概念:Worker(进程).Executor(线程)和Task(Spout.Bolt) 1. 一个worker进程执行的是一个Topol ...
- mybatis学习:mybatis的注解开发和编写dao实现类的方式入门
一.使用注解则不需要创建映射配置文件:即xxxDao.xml javaBean为什么要实现Serializable接口? Java的"对象序列化"能让你将一个实现了Serializ ...
- POJ 1386&&HDU 1116 Play on Words(我以后再也不用cin啦!!!)
Play on Words Some of the secret doors contain a very interesting word puzzle. The team of archaeolo ...
- Leetcode129. Sum Root to Leaf Numbers求根到叶子节点数字之和
给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字. 例如,从根到叶子节点路径 1->2->3 代表数字 123. 计算从根到叶子节点生成的所有 ...
- c++新特性实验(4)声明与定义:右值引用(C++11)
1.作用 c++11以前,临时对象.字面常量一般情况下不可以再次访问,也不可以修改.右值引用可以解决这个问题. 1.1 实验A #include <iostream> using name ...
- LUOGU P3539 [POI2012]ROZ-Fibonacci Representation
传送门 解题思路 打了个表发现每次x只会被比x大的第一个fab或比x小的第一个fab表示,就直接写了个爆搜骗分,结果过了.. 代码 #include<iostream> #include& ...