WPF 自诞生以来就带着微软先生的傲慢。微软说 WPF 支持触摸,于是 WPF 就真的支持触摸了。对,我说的是“支持触摸”,那种摸上去能点能动的;偶尔还能带点儿多指的炫酷效果。但是,WPF 推出那会儿,绝大部分开发者都还没有触摸屏呢,开发个程序要怎么验证支不支持触摸呢?微软先生无奈地决定——你写鼠标的代码就好了,我帮你转换!于是……一大波 BUG 袭来……


WPF 触摸失效的分类

我将 WPF 的触摸失效总结成三种不同的类型。

  1. 触摸下 Stylus/Touch 事件正常触发,但不提升为 Mouse 事件;导致仅使用 Mouse 事件的控件无法使用
  2. 触摸下 Stylus/Touch 有触发,但触发点位置在 (0, 0) 处或上一个触摸点处;导致即使触发了,当前控件也收不到
  3. 触摸下无 Stylus/Touch 事件,也不提升为 Mouse 事件,但鼠标下有 Mouse 事件;导致整个界面完全无法触摸使用

第一种情况

使用触摸或者触笔操作时,如果 Up 事件中发生了任何异常,会导致 StylusLogic.PostProcessInput 的后续逻辑不会正确执行,这就包括了用于清理触控资源的 StylusTouchDevice.OnDeactivate 方法。需要注意的是:Up 事件不止是 TouchUp 或者 StylusUpMouseUp 也会引发这样的触摸失效。

而在 StylusTouchDevice.OnDeactivate 方法中,会重置 StylusLogic.CurrentMousePromotionStylusDevice 属性为 nullNoMousePromotionStylusDevice。此方法不执行会直接导致 StylusLogic.ShouldPromoteToMouse 方法对当前触控设备的判断出现错误,持续返回 false,即不会再执行触控转鼠标的逻辑,出现触摸无效的现象。

第二种情况

如果 WPF 的 StylusUp 事件被阻断(例如 e.Handled = true,或者在 StylusUp 事件中弹出一个模态窗口),则下一次触摸时获取到的点坐标将是上一次被阻断时的点坐标。于是,阻断后的第一次点击必将点中之前点的那个点,而不管现在点中了什么。如果阻断时点在新窗口外,则几乎相当于触摸失效。需要注意的是,这种情况下 MouseUpe.Handled = true 是可以使用而不会导致触摸失效的。

第三种情况

WPF 程序在启动期间,如果触摸组件发生了异常,极有可能会使得触摸根本就没有初始化成功!

比如,System.Windows.Input.StylusLogic.RegisterStylusDeviceCore(StylusDevice stylusDevice) 方法在启动时抛出 System.InvalidOperationException,虽然内部有 catch,但实际获取到的 TabletDevice 个数是 0 个,根本无法获取触摸设备,于是触摸无效。

或者,在 WorkerOperationGetTabletsInfo.OnDoWork 方法中,获取到了错误的触摸设备个数:

IPimcManager pimcManager = UnsafeNativeMethods.PimcManager;
uint count;
pimcManager.GetTabletCount(out count);

解决之道

目前为止,这三种问题都没有根本的解决办法,但是我们可以规避。

第一种情况

我们没有办法阻止每一处的 Up 事件,所以我的做法是在禁止那些可能会在 Up 中引发异常的操作监听 Up 事件,而是统一由我封装好的 Down/Move/Up 中进行分发。在我的 Upcatch 所有异常,随后延迟引发。

try
{
// 分发真正业务上的 Up 事件。
DeliverUpEvent(e);
}
catch (Exception ex)
{
// 使用触摸或者触笔操作时,如果 Up 事件中发生了任何异常,会导致 StylusLogic.PostProcessInput 的后续逻辑不会正确执行,
// 这就包括了用于清理触控资源的 StylusTouchDevice.OnDeactivate 方法。
//
// 而在 StylusTouchDevice.OnDeactivate 方法中,会重置 StylusLogic.CurrentMousePromotionStylusDevice 属性
// 为 null 或 NoMousePromotionStylusDevice。此方法不执行会直接导致 StylusLogic.ShouldPromoteToMouse 方法
// 对当前触控设备的判断出现错误,持续返回 false,即不会再执行触控转鼠标的逻辑,出现触摸无效的现象。
//
// 这里通过 InvokeAsync 的方式再次抛出异常是为了在保证 Stylus 逻辑不出错的情况下,将异常暴露。
Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
ExceptionDispatchInfo.Capture(ex).Throw();
});
}

第二种情况

一样的,我们没有办法阻止每一处的 Up 事件。于是我们只能要求多人开发项目中的每一位开发人员都注意不要在 StylusUpe.Handled = true

然而,要求每一个人都这么做是不现实的,尤其是团队成员不稳定的情况下。目前我还没有找到具体可实施的自动化的解决办法,不过我最近正在尝试的 Roslyn 扩展可能可以解决这样的问题。有关 Roslyn 扩展的开发,可以阅读我的另一篇文章:Roslyn 入门:使用 Roslyn 静态分析现有项目中的代码

第三种情况

启动时触摸设备获取错误的问题我还没有一个彻底的解决方案,目前是检测第一次机会异常,并在发现错误堆栈是以上情况的时候重新启动应用程序。能够采取这样的策略是因为此异常发生在我们的 App 类初始化之后 MainWindow 显示出来之前。

更多的想法

期待你有更多的想法,我希望在我们的交流之下,能够帮助更多人发现和解决 WPF 的触摸失效问题,甚至更多 WPF 的疑难杂症。

WPF 程序无法触摸操作?我们一起来找原因和解决方法!的更多相关文章

  1. qt 旧项目编译运行提示 “启动程序失败,路径或者权限错误?” 原因及解决方法

    qt 旧项目编译运行提示 "启动程序失败,路径或者权限错误?" 原因及解决方法 原因 Qt Creator在打开项目文件的同时会生成.pro.user文件,.pro.user文件叫 ...

  2. #747 –在WPF程序的触摸操作中使用惯性移动 (Implementing Inertia during Touch Manipulation)

    原文:#747 –在WPF程序的触摸操作中使用惯性移动 (Implementing Inertia during Touch Manipulation) 原文地址:https://wpf.2000th ...

  3. 应用程序无法正常启动提示错误0xc000007b 问题的原因和解决方法

    应用程序无法正常启动提示错误0xc000007b 问题的原因和解决方法 前提条件: 你使用的是VS201x软件编写程序,你使用的电脑是X64位的,并且你在使用OpenCV库.你编写的程序可以正常编译, ...

  4. Java程序的编写与执行、Java新手常见问题及解决方法|乐字节Java学习

    今天,我们来写一段Java程序.然后看看Java程序是如何执行的,以及Java新手小白遇到的问题和解决办法.   一.HelloWorld的编写 ① 新建一个XXX.java (文件的扩展名显示出来) ...

  5. 【系统Configmachine.config与自己的应用程序的App.config/Web.Config配置节点重复】解决方法

    自己的应用程序的App.config或Web.Config文件中与系统的C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Configmachine.co ...

  6. "无法启动程序,因为计算机中丢失*.dll” 运行exe错误解决方法

    笔者把编译生成的win32 Release exe文件复制到另外一台电脑上,却发现程序不能运行,报错如下: 报错提示缺失动态链接库pcl_common_release.dll,那为什么在编译生成的电脑 ...

  7. Xcode6 运行程序后,右侧Debug区域的Memory显示空白解决方法

    http://chenyh-blog.com/%E8%9B%8B%E7%96%BC%E7%9A%84%E5%86%85%E5%AD%98-%E7%AC%AC%E4%B8%89%E7%AF%87-sdw ...

  8. 小程序上拉下拉共存时不可使用scroll-view的解决方法

    使用 bindscrolltolower ,必须搭配使用的 scroll-view 会导致小程序 "enablePullDownRefresh": true 下拉不能使用. 解决方 ...

  9. C#调用dll提示"试图加载格式不正确的程序"原因及解决方法

    转载:https://blog.csdn.net/songyi160/article/details/51354660 程序在32位操作系统上运行正常,在64位操作系统上运行读卡功能提示”试图加载格式 ...

随机推荐

  1. servlet初始化参数

    使用<context-param>标签初始化的参数是被应用程序中所有的servlet所共享.但是有时候我们需要为某一个特定的servlet配置参数,这个时候我们就需要使用servlet初始 ...

  2. Codeforces Round #279 (Div. 2) B. Queue

    B. Queue time limit per test 2 seconds memory limit per test 256 megabytes input standard input outp ...

  3. 讲一下numpy的矩阵特征值分解与奇异值分解

    1.特征值分解 主要还是调包: from numpy.linalg import eig 特征值分解:  A = P*B*PT  当然也可以写成 A = QT*B*Q  其中B为对角元为A的特征值的对 ...

  4. 【转】爬取豆瓣电影top250提取电影分类进行数据分析

    一.爬取网页,获取需要内容 我们今天要爬取的是豆瓣电影top250页面如下所示: 我们需要的是里面的电影分类,通过查看源代码观察可以分析出我们需要的东西.直接进入主题吧! 知道我们需要的内容在哪里了, ...

  5. ubuntu14.04安装hadoop2.6.0(伪分布模式)

    版本:虚拟机下安装的ubuntu14.04(64位),hadoop-2.6.0 下面是hadoop2.6.0的官方英文教程: http://hadoop.apache.org/docs/r2.6.0/ ...

  6. flask学习(十):模板中访问模型和字典的属性

    访问模型中的属性或者是字典,可以通过{{params.property}}的形式,或者是使用{{params['age']}}这样的形式

  7. SPOJ-CLEANRBT-状压dp

    CLEANRBT - Cleaning Robot #dynamic-programming #bfs Here, we want to solve path planning for a mobil ...

  8. 玲珑oj 1129 ST

    1129 - 喵哈哈村的战斗魔法师丶坏坏い月 Time Limit:3s Memory Limit:256MByte Submissions:490Solved:107 DESCRIPTION 坏坏い ...

  9. lombok --- 常用注解解析

    @Data@Getter @Setter @ToString@Cleanup@NonNull@Builder@EqualsAndHashCode      

  10. APUE学习笔记——10信号——信号接口函数 signal 和 sigaction

    signal函数     signal函数是早起Unix系统的信号接口,早期系统中提供不可靠的信号机制.在后来的分支中,部分系统使用原来的不可靠机制定义signal函数,如 Solaris 10 .而 ...