原文:2019-11-29-WPF-从触摸消息转触摸事件

title author date CreateTime categories
WPF 从触摸消息转触摸事件
lindexi
2019-11-29 08:47:55 +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-11-29-WPF-从触摸消息转触摸事件的更多相关文章

  1. 2019-5-13-WPF-从触摸消息转触摸事件

    title author date CreateTime categories WPF 从触摸消息转触摸事件 lindexi 2019-05-13 09:43:48 +0800 2019-05-12 ...

  2. 2019.11.29 SAP SMTP郵件服務器配置 發送端 QQ郵箱

    今天群裏的小夥伴問了如何配置郵件的問題,隨自己在sap裏面配置了一個 1.    RZ10配置參數 a)       参数配置前,先导入激活版本 执行完毕后返回 b)      输入参数文件DEFAU ...

  3. 2019.11.29 Mysql的数据操作

    为名为name的表增加数据(插入所有字段) insert into name values(1,‘张三’,‘男’,20); 为名为name的表增加数据(插入部分字段) insert into name ...

  4. pycharm+anaconda在Mac上的配置方法 2019.11.29

    内心os: 听人说,写blog是加分项,那他就不是浪费时间的事儿了呗 毕竟自己菜还是留下来东西来自己欣赏吧 Mac小电脑上进行python数据开发环境的配置 首先下载Anaconda,一个超好用的数据 ...

  5. Supervision meeting notes 2019/11/29

    topic 分支:  1. subgraph/subsequence mining Wang Jin, routine behavior/ motif. Philippe Fournier Viger ...

  6. EOJ Monthly 2019.11 E. 数学题(莫比乌斯反演+杜教筛+拉格朗日插值)

    传送门 题意: 统计\(k\)元组个数\((a_1,a_2,\cdots,a_n),1\leq a_i\leq n\)使得\(gcd(a_1,a_2,\cdots,a_k,n)=1\). 定义\(f( ...

  7. 通过 AppSwitch 禁用 WPF 内置的触摸让 WPF 程序可以处理 Windows 触摸消息

    原文:通过 AppSwitch 禁用 WPF 内置的触摸让 WPF 程序可以处理 Windows 触摸消息 WPF 框架自己实现了一套触摸机制,但同一窗口只能支持一套触摸机制,于是这会禁用系统的触摸消 ...

  8. 2dx关于js响应layer触摸消息的bug

    cocos2dx关于js响应layer触摸消息的bug cocos2d-x 3.7 问题描述: 目前这个版本中(3.7),c++层的layer触摸消息只能通过消息的方式发送给js,不能像lua一样直接 ...

  9. WPF处理Windows消息

    WPF中处理消息首先要获取窗口句柄,创建HwndSource对象 通过HwndSource对象添加消息处理回调函数. HwndSource类: 实现其自己的窗口过程. 创建窗口之后使用 AddHook ...

随机推荐

  1. pycharm 配置使用 flake8 进行语法检测

    打开 PyCharm 在 Terminal 处输入 pip install flake8 在 File ->Settings ->Tools->External Tools 添加一个 ...

  2. 2019CCPC 秦皇岛 E.Escape

    传送门 题意: 给出一个\(n*m\)的迷宫,有\(a\)个入口,\(b\)个出口. 现在有\(a\)个机器人都从入口出发,一开始方向默认为下,你可以选在在一些格子上面放置一个转向器,转向器有四种: ...

  3. fitEllipse的外接矩形与拟合的椭圆参数关系

    根据我看的博客,fitEllipse返回的外接矩形(假设为box),对应椭圆的相应参数: box.size.width 和box.size.height对应椭圆的长轴和短轴: box.center对应 ...

  4. Celery详解(2)

    除了redis,还可以使用另外一个神器----Celery.Celery是一个异步任务的调度工具. Celery是Distributed Task Queue,分布式任务队列,分布式决定了可以有多个w ...

  5. 201871010135 张玉晶《面向对象程序设计(java)》第十四周学习总结

    项目 内容 这个作业属于哪个过程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.com/zyja/p/11963 ...

  6. Spring Data介绍

    Spring Data是Spring 的一个子项目.用于简化数据库访问,支持NoSQL和关系数据库存储.其主要目标是使数据库的访问变得方便快捷. Spring Data 项目所支持NoSQL存储: M ...

  7. TCP协议的粘包问题(八)

    一.什么是粘包 在socket缓冲区和数据的传递过程介绍中,可以看到数据的接收和发送是无关的,read()/recv() 函数不管数据发送了多少次,都会尽可能多的接收数据.也就是说,read()/re ...

  8. COSO企业风险管理框架及其在大宗商品行业的应用

    https://mp.weixin.qq.com/s/P1NDvqsz0GNObm1pb47mfg 中国期货市场交易量领先全球,期权.互换等新的衍生品工具逐步引入,场外衍生品服务商正在涌现.越来越多的 ...

  9. SpringBoot中的日志

    默认情况下,Spring Boot会用SLF4J + Logback来记录日志,并用INFO级别输出到控制台. SLF4J,即简单日志门面(Simple Logging Facade for Java ...

  10. Docker、Kubernetes的 CICD实现思路

    from:https://www.jianshu.com/p/654505d42180