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-从触摸消息转触摸事件的更多相关文章

  1. 2019-11-29-WPF-从触摸消息转触摸事件

    原文:2019-11-29-WPF-从触摸消息转触摸事件 title author date CreateTime categories WPF 从触摸消息转触摸事件 lindexi 2019-11- ...

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

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

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

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

  4. WPF处理Windows消息

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

  5. 第13讲- Android之消息提示Notification

    第13讲 Android之消息提示Notification .Notification Notification可以理解为通知的意思一般用来显示广播信息,通知可以显示到系统的上方的状态栏(status ...

  6. 每日一练ACM 2019.04.13

    2019.04.13 第1002题:A+B Proble Ⅱ Problem DescriptionI have a very simple problem for you. Given two in ...

  7. Windows 消息循环(2) - WPF中的消息循环

    接上文: Windows 消息循环(1) - 概览 win32/MFC/WinForm/WPF 都依靠消息循环驱动,让程序跑起来. 本文介绍 WPF 中是如何使用消息循环来驱动程序的. 4 消息循环在 ...

  8. WPF 获得触摸精度和触摸点

    原文:WPF 获得触摸精度和触摸点 本文主要告诉大家如何获得所有的触摸设备的触摸精度和触摸点数. 需要通过反射的方法才可以拿到触摸的精度. 使用 Tablet.TabletDevices 可以获得所有 ...

  9. C# WPF QQ新消息托盘悬浮窗效果实现

    原文:C# WPF QQ新消息托盘悬浮窗效果实现 今天在做一个项目的时候需要这么一个效果,但是网上找了一会发现并没有现成的给我参考(复制),但是呢,我千(到)辛(处)万(抄)苦(袭)想(复)破(制)头 ...

随机推荐

  1. java最常用的几种加密算法

    1. BASE64 Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,大家可以查看RFC2045-RFC2049,上面有MIME的详细规范.Base64编码可用于在HTTP环境下传递 ...

  2. Ubuntu18上安装Go和GoLand

    第一步骤:安装Go 方式一: 使用 sudo apt-get install golang命令安装 ubuntu软件库里当前golang版本为1.10,(golang最新版为1.11),可满足要求. ...

  3. 解决使用mybatis模糊查询为空的问题

    解决方法: 在数据库配置的url后添加?useUnicode=true&characterEncoding=utf-8 参考: https://blog.csdn.net/IT_private ...

  4. CodeChef:Chef and Problems(分块)

    CodeChef:Chef and Problems 题目大意 有一个长度为n的序列$a_1,a_2,……,a_n$,每次给出一个区间[l,r],求在区间内两个相等的数的最远距离($max(j-i,满 ...

  5. “玲珑杯”ACM比赛 Round #11 B题

    http://www.ifrog.cc/acm/problem/1097?contest=1013&no=1 //LIS的高端写法 #include <iostream> #inc ...

  6. 第一个WindowService服务

    背景:Web项目中需要定时执行一段程序 方法: 1.新建一个WindowService项目 2.添加代码 public partial class Service1 : ServiceBase { S ...

  7. JS中对象转数组方法总结

    1.Array.from() 方法,用于数组的浅拷贝.就是将一个类数组对象或者可遍历对象转换成一个真正的数组.eg: let obj = { 0: 'nihao', 1: 'haha', 2: 'ga ...

  8. MyBatis配置文件(四)--typeHandlers

    typeHandlers又叫类型处理器,就像在JDBC中,我们在PreparedStatement中设置预编译sql所需的参数或执行sql后根据结果集ResultSet对象获取得到的数据时,需要将数据 ...

  9. [code]图像亮度调整enhancement

    //draft 2013.9 //F=X2/u; ////远处细节被淹没. 亮的地方增亮明显,暗的地方更暗. 不可取. // CvScalar rgb; // rgb=cvAvg(src); //fo ...

  10. linux命令统计文件中某个字符串出现的次数

    1.使用grep linux grep命令在我的随笔linux分类里有过简单的介绍,这里就只简单的介绍下使用grep命令统计某个文件这某个字符串出现的次数,首先介绍grep命令的几个参数,详细参数请自 ...