Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >WPF dotnet 6 开启 PM v2 的 DPI 感知 导致触摸线程访问 UI 属性抛异常

WPF dotnet 6 开启 PM v2 的 DPI 感知 导致触摸线程访问 UI 属性抛异常

作者头像
林德熙
发布于 2022-08-12 11:55:44
发布于 2022-08-12 11:55:44
67200
代码可运行
举报
文章被收录于专栏:林德熙的博客林德熙的博客
运行总次数:0
代码可运行

本文记录一个 WPF 在 dotnet 6 的一个已知问题,且此问题我已修复提交给官方仓库。这是一个只有在 dotnet 6 框架下,非 dotnet 5 也非 .NET Core 3.1 也非 .NET Framework 的问题,要求开启 DPI 感觉等级为 PerMonitorV2 的特性,在带触摸屏上的应用,应用运行过程中,切换屏幕的 DPI 之后,触摸过程有概率触发在触摸线程访问 UI 的依赖属性,在触摸线程抛出异常炸掉应用

条件

必须同时满足以下条件:

  • dotnet 6: dotnet 6.0.1 及以上版本
    • dotnet 5 和 .NET Core 3.1 和 .NET Framework 没有此问题,这是新改出来的,细节请参阅原理部分
  • 应用开启 PerMonitorV2 的特性
  • 应用开启 StylusPlugIn 的支持
  • 在触摸设备上运行,进行触摸交互
  • 应用运行过程存在切换系统的 DPI 的值
    • 需要先运行应用,对应用进行触摸交互,再切换,再触摸
    • 可以选择多个屏幕不同的 DPI 让 WPF 在多个屏幕来回移动和触摸
    • 可以选择一个屏幕,在运行应用过程切换 DPI 的值

这也算是一个好消息,要求很严格,而且在用户端,很多都是只有一个屏幕。再加上切换 DPI 系统会提示要重启电脑,重启电脑就不会存在此问题。也就是说这个问题影响其实是比较小的

最后也是最重要的是,这个 Bug 不是必复现的,也许你需要很多次测试才可以遇到,详细请参阅下面步骤

步骤

如以上条件,在 Win10 的 1703 以上版本运行,通过 支持 Windows 10 最新 PerMonitorV2 特性的 WPF 多屏高 DPI 应用开发 - walterlv 博客的方法给应用开启 PM v2 的功能

根据以上条件,给应用附加上 StylusPlugIn 的支持,方法请参阅 附加 StylusPlugIn 的例子

准备完成之后,执行以下步骤

  1. 启动应用,进行触摸
  2. 接着打开设置,点击屏幕选项卡,修改缩放和布局的 更改文本、应用等项目的大小,修改百分比
  3. 切换回应用,继续触摸应用

这是一个非必定复现的坑,需要多次循环以上步骤,也许才能遇到此坑。行为是在触摸线程 Stylus Input 线程将会因为调用的 GetAndCacheTransformToDeviceMatrix 方法碰了 UI 线程的属性,抛出如下异常

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Application: Application.exe
CoreCLR Version: 6.0.121.56705
.NET Version: 6.0.1
Description: The process was terminated due to an unhandled exception.
Exception Info: System.InvalidOperationException: The calling thread cannot access this object because a different thread owns it.
   at System.Windows.Threading.Dispatcher.ThrowVerifyAccess()
   at System.Windows.Threading.Dispatcher.VerifyAccess()
   at System.Windows.Threading.DispatcherObject.VerifyAccess()
   at System.Windows.Media.CompositionTarget.VerifyAPIReadOnly()
   at System.Windows.Interop.HwndTarget.get_TransformToDevice()
   at System.Windows.Input.StylusLogic.GetAndCacheTransformToDeviceMatrix(PresentationSource source)
   at System.Windows.Input.StylusWisp.WispLogic.GetTabletToViewTransform(PresentationSource source, TabletDevice tabletDevice)
   at System.Windows.Input.PenContexts.InvokeStylusPluginCollection(RawStylusInputReport inputReport)
   at System.Windows.Input.StylusWisp.WispLogic.InvokeStylusPluginCollection(RawStylusInputReport inputReport)
   at System.Windows.Input.StylusWisp.WispLogic.ProcessInputReport(RawStylusInputReport inputReport)
   at System.Windows.Input.StylusWisp.WispLogic.ProcessInput(RawStylusActions actions, PenContext penContext, Int32 tabletDeviceId, Int32 stylusDeviceId, Int32[] data, Int32 timestamp, PresentationSource inputSource)
   at System.Windows.Input.PenContexts.ProcessInput(RawStylusActions actions, PenContext penContext, Int32 tabletDeviceId, Int32 stylusPointerId, Int32[] data, Int32 timestamp)
   at System.Windows.Input.PenContexts.OnPenDown(PenContext penContext, Int32 tabletDeviceId, Int32 stylusPointerId, Int32[] data, Int32 timestamp)
   at System.Windows.Input.PenContext.FirePenDown(Int32 stylusPointerId, Int32[] data, Int32 timestamp)
   at System.Windows.Input.PenThreadWorker.FireEvent(PenContext penContext, Int32 evt, Int32 stylusPointerId, Int32 cPackets, Int32 cbPacket, IntPtr pPackets)
   at System.Windows.Input.PenThreadWorker.ThreadProc()
   at System.Threading.Thread.StartHelper.Callback(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Thread.StartCallback()

如果自己试了几次也没有复现,可以试试用我的版本,保证按照上面步骤,一定挂。我的版本由以下三个 NuGet 包组成

相信想用定制版本的 WPF 的开发者都知道可以使用吧

为什么使用 6.0.4-alpha05-FixTouch01 版本是能一定复现,还请看下面的原理部分

原理

为什么使用 6.0.4-alpha05-FixTouch01 版本是能一定复现,那是因为我改了触摸模块,我修复了触摸偏移问题导致了此问题暴露。为什么有触摸问题?这是因为 Rob LaDuca 大佬在 Fix raw stylus data to support per-monitor DPI by rladuca · Pull Request #2891 · dotnet/wpf 修复了 PM 的触摸问题,然而他的修复引入新的问题。我问他,你有触摸屏测试没,他说没有,不过 WPF 内部有个自动化测试,自动化测试通过就可以了。然而他的更改已合入主干,导致了使用 StylusPlugIn 的触摸存在偏移

我在 Try fix the first point in StylusPlugin in high DPI by lindexi · Pull Request #6428 · dotnet/wpf 修复了以上的触摸偏移问题,但是由于此修复引入了新的问题。修复之前,如 WPF 高速书写 StylusPlugIn 原理 描述,将会在 UI 线程收到触摸之前,先在触摸线程收到。在触摸线程收到时,还没有找到命中的元素,这就导致了拿到的空值,无法处理当前命中到的元素所在的窗口,从而无法了解当前触摸点的 DPI 的参数。于是触摸就因为拿不到 DPI 参数进行计算而偏移

我修复了触摸偏移问题是通过拿触摸输入源的窗口句柄进行获取 DPI 计算。获取触摸的输入源窗口,不需要等待 UI 线程命中测试,于是修复了触摸偏移的问题

然而以上输入引入了新的问题,那就是在开启 PM v2 特性,在 DPI 变更之后,触摸比 UI 线程更快进入 GetAndCacheTransformToDeviceMatrix 方法。 此方法的作用是获取或计算 DPI 换算 Matrix 参数。如果是在 UI 线程先进来,那自然能更新为一个符合预期的值。然而如果是触摸线程先进来,将会由于触摸线程没有从 _transformToDeviceMatrices 字典获取到对应的 DPI 的参数,从而需要获取 TransformToDevice 属性。在获取 TransformToDevice 属性的时候,由于 TransformToDevice 属性默认是限制只有 UI 线程可以访问,于是就抛出了异常

以下是 GetAndCacheTransformToDeviceMatrix 代码,我添加了足够的注释,方便大家了解

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 protected Matrix GetAndCacheTransformToDeviceMatrix(PresentationSource source) 
 { 
 	 // 在当前 dotnet 主干分支上,由于 Rob LaDuca 大佬修复 per-monitor DPI 时,没有考虑到 StylusPlugIn 比 UI 线程更快进入此函数,在首次触摸时,让 PresentationSource 参数为空,从而无法获取到正确的值进行计算,从而计算触摸点由于缺少参数,在 DPI 非 96 情况下偏移 DPI 比例

     var hwndSource = source as HwndSource; 
     Matrix toDevice = Matrix.Identity; 
  
     if (hwndSource?.CompositionTarget != null) 
     {
     	 // 如果更改了 DPI 且开启特性,那么在触摸线程比 UI 线程更快进入此函数时,将会在 _transformToDeviceMatrices 字典里面获取不到参数,需要 触摸线程 计算
         // If we have not yet seen this DPI, store the matrix for it. 
         if (!_transformToDeviceMatrices.ContainsKey(hwndSource.CompositionTarget.CurrentDpiScale)) 
         { 
         	 // 触摸线程获取 TransformToDevice 参数,将会因为 TransformToDevice 参数默认限制只有 UI 线程可以访问从而炸掉
             _transformToDeviceMatrices[hwndSource.CompositionTarget.CurrentDpiScale] = hwndSource.CompositionTarget.TransformToDevice; 
             Debug.Assert(_transformToDeviceMatrices[hwndSource.CompositionTarget.CurrentDpiScale].HasInverse); 
         } 
  
         toDevice = _transformToDeviceMatrices[hwndSource.CompositionTarget.CurrentDpiScale]; 
     } 
  
     return toDevice; 
 } 

问题已反馈给 WPF 官方: WPF tocuh in Window with StylusPlugIn may throw InvalidOperationException · Issue #6829 · dotnet/wpf

少珺 小伙伴的帮助下,我修复了此问题,请看 Fix get TransformToDevice in Stylus Input thread will throw the InvalidOperationException by lindexi · Pull Request #6840 · dotnet/wpf

核心修复的方法是在触摸线程计算,而不是获取 TransformToDevice 属性,这是因为 TransformToDevice 属性的获取方法里面也是一个简单的计算。从性能角度和安全角度都是自己计算会更好

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 public override Matrix TransformToDevice 
 { 
     get 
     { 
         VerifyAPIReadOnly(); 
         Matrix m = Matrix.Identity; 
         m.Scale(CurrentDpiScale.DpiScaleX, CurrentDpiScale.DpiScaleY); 
         return m; 
     } 
 } 

性能上以上的计算可能比从字典获取的性能更好,不过这部分我没有测试

修复方法

最佳修复方法,等待 WPF 的大佬们合入我的修复,分发新的 dotnet 版本,更新版本即可

我所在的团队也分发了私有的 WPF 版本,包含此修复,如果大家也遇到此问题,且等不及我的修复合入主干,可以试试我所在的团队分发的版本,请看 https://www.nuget.org/packages/dotnetCampus.WPF/6.0.4-alpha06-test02

更多文档

更多 DPI 相关请参阅

更多触摸请参阅 WPF 触摸相关

更多关于我博客请参阅 博客导航

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
WPF 升级 .NET Core 的理由
当前我团队的 .NET Framework 使用 4.5 但是如果使用 dotnet core 能使用更多的 API 同时这些 API 都优化了大部分性能
林德熙
2020/04/16
1K0
WPF 已知问题 开启 WM_Pointer 消息之后 获取副屏触摸数据坐标偏移
本文记录 WPF 触摸的一个已知问题,仅在开启 WM_Pointer 消息之后,将应用程序运行在包含多个屏幕的带触摸屏的设备上,如此时在非主屏幕的触摸屏上进行触摸,使用 GetStylusPoint 或 GetIntermediateTouchPoints 方法获取触摸点时,将会发现所获取的触摸点的坐标是偏的,偏的坐标差值刚好是整个屏幕距离
林德熙
2023/12/10
3302
WPF 已知问题 开启 WM_Pointer 消息之后 获取副屏触摸数据坐标偏移
WPF 高速书写 StylusPlugIn 原理 添加 StylusPlugIn 到输入迁移的 StylusPlugInCollection 方法使用 StylusPlugIn
本文告诉大家 WPF 的 StylusPlugIn 为什么能做高性能书写,在我的上一篇博客和大家介绍了 WPF 的触摸原理,但是没有详细告诉大家如何通过触摸原理知道如何去做一个高速获得触摸的应用,所以本文就在上一篇博客的基础继续告诉大家底层的原理 如果觉得原理很无聊,就直接关闭本文,因为本文都是理论,不会告诉大家如何做高性能书写
林德熙
2019/03/13
7300
WPF 高速书写 StylusPlugIn 原理
            添加 StylusPlugIn 到输入迁移的 StylusPlugInCollection 方法使用 StylusPlugIn
WPF 触摸失效 试试重启触摸
在使用一些诡异的系统以及诡异的触摸框的时候,也许会出现 WPF 程序触摸失效,失效的本质原因是 Win32 层应用触摸失效。也许出现的问题是某个窗口设置 TopMost 然后插拔一些触摸设备等,这些行为,如果触摸设备太过诡异,也许就会让 Win32 窗口触摸失效。刚好 WPF 也是一个 Win32 窗口,此时的 WPF 也会触摸失效
林德熙
2021/03/22
1.4K0
dotnet 读 WPF 源代码笔记 从 WM_POINTER 消息到 Touch 事件
本文记录我读 WPF 源代码的笔记,在 WPF 底层是如何从 Win32 的消息循环获取到的 WM_POINTER 消息处理转换作为 Touch 事件的参数
林德熙
2024/09/09
3550
WPF 同一窗口内的多线程 UI(VisualTarget)
发布于 2017-10-30 15:38 更新于 2018-09-05 05:47
walterlv
2018/09/18
2.6K0
WPF 同一窗口内的多线程 UI(VisualTarget)
WPF 触摸到事件
在 WPF 界面框架核心就是交互和渲染,触摸是交互的一部分。在 WPF 是需要使用多个线程来做触摸和渲染,触摸是单独一个线程,这个线程就是只获得触摸,而将触摸转路由是在主线程。
林德熙
2018/09/19
1.3K0
WPF 触摸到事件
Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32)
本文将介绍 Windows 系统中高 DPI 开发的基础知识。由于涉及到坐标转换,这种转换经常发生在计算的不知不觉中;所以无论你使用哪种 Windows 下的 UI 框架进行开发,你都需要了解这些内容,以免不断踩坑。
walterlv
2023/10/22
1K0
Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32)
WPF 多线程 UI:设计一个异步加载 UI 的容器
2018-09-08 12:53
walterlv
2018/09/18
4.1K0
WPF 多线程 UI:设计一个异步加载 UI 的容器
WPF 获取元素(Visual)相对于屏幕设备的缩放比例,可用于清晰显示图片
我们知道,在 WPF 中的坐标单位不是屏幕像素单位,所以如果需要知道某个控件的像素尺寸,以便做一些与屏幕像素尺寸相关的操作,就需要经过一些计算(例如得到屏幕的 DPI)。
walterlv
2023/10/22
7860
WPF 获取元素(Visual)相对于屏幕设备的缩放比例,可用于清晰显示图片
WPF 渲染原理
在 WPF 最主要的就是渲染,因为 WPF 是一个界面框架。想用一篇博客就能告诉大家完整的 WPF 渲染原理是不可能的。本文告诉大家 WPF 从开发者告诉如何画图像到在屏幕显示的过程。本文是从一个很高的地方来看渲染的过程,在本文之后会添加很多博客来告诉大家渲染的细节。
林德熙
2018/09/19
3K0
WPF 渲染原理
WPF 在触摸线程等待主线程窗口关闭会让主线程和触摸线程相互等待 原理方法一方法二
本文是记录一个线程相互等待导致主线程无法响应的问题,这个问题是属于一定可以复现的问题,是 WPF 的已知问题。如果遇到这个问题,属于暂时没有方法解决,只能规避。 这个问题的最简单复现步骤是在触摸线程,也就是 StylusInput 线程,等待一个主线程的窗口关闭,此时就会出现主线程卡住的问题
林德熙
2019/03/13
1.2K0
WPF 同一窗口内的多线程/多进程 UI(使用 SetParent 嵌入另一个窗口)
发布于 2018-07-11 13:35 更新于 2018-07-12 11:44
walterlv
2018/09/18
4.4K0
WPF 同一窗口内的多线程/多进程 UI(使用 SetParent 嵌入另一个窗口)
支持 Windows 10 最新 PerMonitorV2 特性的 WPF 多屏高 DPI 应用开发
发布于 2018-10-22 18:04 更新于 2018-12-14 01:54
walterlv
2020/02/10
1.8K0
WindowsXamlHost:在 WPF 中使用 UWP 的控件(Windows Community Toolkit)
Windows Community Toolkit 再次更新到 5.0。以前可以在 WPF 中使用有限的 UWP 控件,而现在有了 WindowsXamlHost,则可以使用更多 UWP 原生控件了。
walterlv
2020/02/10
4.8K0
WPF 已知问题 在 WIC 层处理异常图片时 可能由于出现未处理异常导致进程退出
本文记录一个已知问题,此问题预计和 WPF 只有一毛钱关系,本质问题是在 WIC 层的 WindowsCodecs.dll 或 CLR 层上。在一些奇怪的系统上,解码一些奇怪的图片时,可能在解码器层抛出未捕获的本机异常,从而导致进程退出
林德熙
2023/12/06
2570
dotnet 代码调试方法
本文将会从简单到高级,告诉大家如何调试 dotnet 的代码,特别是桌面端。本文将会使用到 VisualStudio 大量的功能,通过各种好用的功能提高调试方法
林德熙
2019/06/15
1.5K0
WPF 修复 ContextMenu 在开启 PerMonitorV2 后所用 DPI 错误
本文告诉大家如何修复 WPF 的 ContextMenu 在开启 PerMonitorV2 之后,在双屏不同的 DPI 的设备上,在副屏弹出的 ContextMenu 使用了主屏的 DPI 导致缩放错误的问题
林德熙
2023/04/07
4240
WPF 开机启动因为触摸初始化锁住界面显示
现象是设置 WPF 开机启动的时候,概率界面不显示,进程已经起来,同时占用内存极小。通过 dump 或附加调试可以看到主进程带等待触摸线程的回应
林德熙
2020/07/06
9170
WPF 已知问题 包含 NaN 的 Geometry 几何可能导致渲染层抛出 UCEERR_RENDERTHREADFAILURE 异常
本文记录一个 WPF 已知问题,当传入到渲染的 Geometry 几何里面包含了 NaN 数值,将可能让应用程序收到从渲染层抛上来的 UCEERR_RENDERTHREADFAILURE 异常,且此异常缺乏必要信息,比较难定位到具体错误逻辑
林德熙
2023/12/13
6520
推荐阅读
相关推荐
WPF 升级 .NET Core 的理由
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验