WPF 开启Pointer消息存在的坑

news/2024/10/3 17:19:39

本文记录在 WPF 开启 Pointer 消息的坑

屏幕键盘

启用了Pointer之后,调用 TextBox.Focus() 方法时,有一定的可能起不来屏幕键盘,必须点在控件之上才行,触摸在它之上才行

后续的 Win10 版本似乎修复了这个问题,暂时还没了解到具体是从哪个版本开始修复

使用屏幕绝对坐标而不是窗口坐标

默认 Pointer 消息是使用屏幕绝对坐标而不是窗口坐标

可能存在获取 Stylus 事件时触摸点不准,此时可以通过获取 Touch 代替,详细请看 WPF will have a touch offset after trun on the WM_Pointer message · Issue #3360 · dotnet/wpf 此问题应该在 Fix raw stylus data to support per-monitor DPI by rladuca · Pull Request #2891 · dotnet/wpf 修复

开启 Pointer 消息之后无法隐藏触摸反馈点

开启 Pointer 消息之后,调用 Stylus.IsPressAndHoldEnabled="False" 无效

在没有开启 Pointer 消息,将会在 System.Windows.Interop.HwndSource 的 Initialize 方法通过判断是否开启 Pointer 消息执行 HwndStylusInputProvider 逻辑

            if (StylusLogic.IsStylusAndTouchSupportEnabled){// Choose between Wisp and Pointer stacksif (StylusLogic.IsPointerStackEnabled){// 开启 Pointer 的逻辑_stylus = new SecurityCriticalDataClass<IStylusInputProvider>(new HwndPointerInputProvider(this));}else{_stylus = new SecurityCriticalDataClass<IStylusInputProvider>(new HwndStylusInputProvider(this));}}

在 HwndStylusInputProvider 将会读取 IsPressAndHoldEnabledProperty 属性,然后使用 WM_TABLET_QUERYSYSTEMGESTURESTATUS 返回 1 的方式告诉系统不显示触摸反馈点。也就是 WPF 隐藏触摸反馈点是通过 How do I disable the press-and-hold gesture for my window 的方法

如果不设置 Stylus.IsPressAndHoldEnabled="False" 也可以自己手动监听消息,在消息 WM_TABLET_QUERYSYSTEMGESTURESTATUS 里面返回 1 就可以告诉系统不显示触摸反馈点

       private IntPtr Hook(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled){const int WM_TABLET_DEFBASE =0x02C0;const int WM_TABLET_QUERYSYSTEMGESTURESTATUS = WM_TABLET_DEFBASE + 12;const int WM_TABLET_FLICK = WM_TABLET_DEFBASE + 11;if (msg == WM_TABLET_QUERYSYSTEMGESTURESTATUS){uint flags = 0;flags |= TABLET_PRESSANDHOLD_DISABLED;flags |= TABLET_TAPFEEDBACK_DISABLED;flags |= TABLET_TOUCHUI_FORCEON;flags |= TABLET_TOUCHUI_FORCEOFF;flags |= TABLET_FLICKS_DISABLED;handled = true;return new IntPtr(flags);}else if (msg == WM_TABLET_FLICK){handled = true;return new IntPtr(1);}return IntPtr.Zero;}private const uint TABLET_PRESSANDHOLD_DISABLED = 0x00000001;private const uint TABLET_TAPFEEDBACK_DISABLED = 0x00000008;private const uint TABLET_TOUCHUI_FORCEON = 0x00000100;private const uint TABLET_TOUCHUI_FORCEOFF = 0x00000200;private const uint TABLET_FLICKS_DISABLED = 0x00010000;

但如果开启了 Pointer 消息,那么这个机制将会无效,即使依然是手动监听消息,如 https://github.com/lindexi/lindexi_gd/tree/81b2a63a/KemjawyecawDurbahelal 的代码,也是无效的

问题报告给了 WPF 官方,请看 WPF can not work well with set IsPressAndHoldEnabled to false when enable pointer message · Issue #3379 · dotnet/wpf 但预计不会在 WPF 中修复,原因是这是 Windows 的 WM_Pointer 机制的坑,和 WPF 其实没有关系

现在 WM_Pointer 开启之后,可以通过 DwnShowContact 达成类似的功能,或者是通过 SetWindowFeedbackSetting 禁用整个窗口的触摸反馈效果

另一个解决方法是在关闭系统全局触摸反馈点,关闭方法请看 3 Ways to Enable or Disable Touch Feedback in Windows 10

不存在互斥触摸交互

其实这个也算是一个特性,但是行为有变更。在 Win10 提出的一个新交互里面,允许未激活的窗口接收到鼠标滚轮消息。这一套是和 Pointer 一起提出的,我问了微软的大佬,收到了 MVP 内部邮件,可惜我没看明白,大概的意思是这个交互是 Win10 提供的,和 Pointer 走的是差不多的逻辑

这也就导致了原本支持互斥独占的触摸交互,在开启 Pointer 的应用下被无效。表现是如当前触摸被某个获取焦点的窗口捕获,此时触摸点到一个后台的窗口,未激活的窗口上,那此窗口依然可以收到触摸消息,无论这个窗口是在哪个进程上,只需要此窗口所在的进程开启 Pointer 消息即可

而原先的交互是如果触摸被某个前台窗口捕获,那么其他窗口将啥都收不到,包括 WM_Touch 消息或者实时触摸消息

滑动过程开启窗口触摸失效

在进行 Manipulation 过程中,打开或者激活了窗口,将导致此窗口不接受触摸消息而触摸失效。例如另一个进程的文本框获取焦点时,在滑动 ListView 列表时,打开了窗口或者激活现有的窗口到前台获取焦点,在此窗口内进行触摸,可能会收不到触摸事件

原因是在进行 Manipulation 将会设置一些特殊的内部字段参数,原本不走 Pointer 时,将会自然走到 MouseDevice.cs 的逻辑,触发了 Activate 逻辑,让 WPF 框架层处理窗口激活交互逻辑。但是在 Pointer 层时,走的是 PointerLogic.cs 的逻辑,没有激活交互的逻辑。修复方法是在 PointerLogic.cs 的逻辑也调用 MouseDevice.cs 的 PushActivateInputReport 方法激活交互

此问题已修复,参阅 Port touch activation fix from 4.8 by SamBent · Pull Request #5836 · dotnet/wpf

以上是在 .NET Core 版本的修复,对应的 .NET Framework 在 2022 的一月系统质量更新补丁,如 50088XX 系列补丁,参阅 https://support.microsoft.com/kb/5008890

.NET Framework January 2022 Security and Quality Rollup Updates - .NET Blog

触摸偏移

WPF 已知问题 开启 WM_Pointer 消息之后 获取副屏触摸数据坐标偏移

开启 RegisterTouchWindow 不用禁用实时触摸依然可以收到消息

在没有开启 WM_Pointer 消息之前,需要先禁用实时触摸才能收到 WM_Touch 消息,详细请看 WPF 禁用实时触摸

然而在开启 POINTER 消息之后,只要调用 RegisterTouchWindow 方法,即可让窗口收到 WM_TOUCH 消息。如以下测试代码所示

public partial class MainWindow : Window
{public MainWindow(){InitializeComponent();SourceInitialized += OnSourceInitialized;}private void OnSourceInitialized(object? sender, EventArgs e){var windowInteropHelper = new WindowInteropHelper(this);var hwnd = windowInteropHelper.Handle;PInvoke.RegisterTouchWindow(new HWND(hwnd), 0);HwndSource source = HwndSource.FromHwnd(hwnd)!;source.AddHook(Hook);}private unsafe IntPtr Hook(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled){if ((uint)msg is PInvoke.WM_TOUCH){var touchInputCount = wparam.ToInt32();var pTouchInputs = stackalloc TOUCHINPUT[touchInputCount];if (PInvoke.GetTouchInputInfo(new HTOUCHINPUT(lparam), (uint)touchInputCount, pTouchInputs,sizeof(TOUCHINPUT))){for (var i = 0; i < touchInputCount; i++){var touchInput = pTouchInputs[i];var point = new System.Drawing.Point(touchInput.x / 100, touchInput.y / 100);PInvoke.ScreenToClient(new HWND(hwnd), ref point);Debug.WriteLine($"Touch {touchInput.dwID} XY={point.X}, {point.Y}");}PInvoke.CloseTouchInputHandle(new HTOUCHINPUT(lparam));}}return IntPtr.Zero;}
}

只需要按照 WPF dotnet core 如何开启 Pointer 消息的支持 提供的方法,在 App 构造函数里面使用如下代码进行开启 WM_POINTER 消息,和对比不开启的调试输出,即可看到调试下的输出差别

    public App(){AppContext.SetSwitch("Switch.System.Windows.Input.Stylus.EnablePointerSupport", true);}

以上代码放在 github 和 gitee 上,可以使用如下命令行拉取代码。我整个代码仓库比较庞大,使用以下命令行可以进行部分拉取,拉取速度比较快

先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 6354ef906a85be0d2cdcb6d545c094b098c34544

以上使用的是国内的 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码。如果依然拉取不到代码,可以发邮件向我要代码

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 6354ef906a85be0d2cdcb6d545c094b098c34544

获取代码之后,进入 WPFDemo/GibibealaFoheyufairha 文件夹,即可获取到源代码

更多阅读

WPF 如何确定应用程序开启了 Pointer 触摸消息的支持

win10 支持默认把触摸提升 Pointer 消息

WPF dotnet core 如何开启 Pointer 消息的支持

WPF can not work well with set IsPressAndHoldEnabled to false when enable pointer message · Issue #3379 · dotnet/wpf

Stylus.SetIsPressAndHoldEnabled does not work when Switch.System.Windows.Input.Stylus.EnablePointerSupport is enabled · Issue #5939 · dotnet/wpf

How do I disable the press-and-hold gesture for my window

WM_TABLET_QUERYSYSTEMGESTURESTATUS message (Tpcshrd.h)

WM_TABLET_FLICK message (Tpcshrd.h)

c# - Calling SetGestureConfig method affects onmousemove override of control - Stack Overflow

SetGestureConfig 函数-中文整理_Augusdi的专栏-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/55217.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

MIUI系统,APKMirror Installer安装apkm的时候提示app installation failed Installation aborted解决方案

场景 我的手机是MIUI系统,通过APKMirror Installer安装apkm的时候提示app installation failed Installation aborted。 本来不想装了,心想可能是版本的兼容问题,但是我查看的SDK的版本和我的android是匹配的,不应该会失败,那是为什么呢? 解决方案 禁用掉开发者选项中的启…

Serilog文档翻译系列(三) - 基础配置

Serilog基础配置:创建日志记录器、接收器、输出模板、最低级别、覆盖每个接收器、增强器、过滤器、子日志记录器 Serilog 使用简单的 C# API 来配置日志记录。当需要外部配置时,可以(慎用)通过使用 Serilog.Settings.AppSettings 包或 Serilog.Settings.Configuration 包进…

全网最适合入门的面向对象编程教程:42 Python常用复合数据类型-collections容器数据类型

在Python中,collections模块提供了一组高效、功能强大的容器数据类型,扩展了内置的基础数据类型(如list、tuple、dict等),这些容器数据类型在处理特定问题时,能够提供更简洁、更高效的解决方案。全网最适合入门的面向对象编程教程:42 Python 常用复合数据类型-collectio…

MIT6.S081(2023 Fall) Lab2 Lab3 总结

Lab1 可以说就是一些编程相关的工作,只是程序中有一些操作系统相关的概念(例如进程、管道)。做完Lab1之后我有一个问题:系统调用时如何进行的,为什么我在user下调用sleep( ),就可以直接调用到内核中的sleep代码,我并没有看到两者是如何联系的。做完Lab2,这个问题得到了…

稍微改一下 Wiki.js 的界面 CSS

Wiki.js 默认样式那个样子真的太丑了,又黑又蓝。。。反正我自己不太中意就写了覆盖样式换了 换了纯白的风格,用在自己的站了,样子见下:相关链接仓库:https://github.com/AurLemon/wikijs-citizen-styles 下载:https://github.com/AurLemon/wikijs-citizen-styles/release…

第四天---RSA进阶题型

T1.小明文攻击 一.题目: from Crypto.Util.number import * from gmpy2 import *flag = bNSSCTF{******}p = getPrime(5120) q = getPrime(5120)n = p*q e = 97 phi = (p-1)*(q-1)m = bytes_to_long(flag) c = powmod(m, e, n)print(fn = {n}) print(fe = {e}) print(fc = {c}…

电路基础 ---- 负反馈放大电路的方框图分析法

1 方框图分析法 方框图如下:图中\(A_{uo}\)是一个电压输入的放大器的放大倍数,称为开环放大倍数。 \(F\)为反馈系数,是一个矢量,是指输出信号\(x_{o}\)的多少倍回送到放大器的输入端。 \(M\)为衰减系数,也是一个矢量,是指输入信号的多少倍,进入放大器的输入端。根据上述…