在使用一些诡异的系统以及诡异的触摸框的时候,也许会出现 WPF 程序触摸失效,失效的本质原因是 Win32 层应用触摸失效。也许出现的问题是某个窗口设置 TopMost 然后插拔一些触摸设备等,这些行为,如果触摸设备太过诡异,也许就会让 Win32 窗口触摸失效。刚好 WPF 也是一个 Win32 窗口,此时的 WPF 也会触摸失效

这个方法因为过于强,我建议只有你在尝试过其他方法无法修复之后才能使用。本文的方法修复触摸是根据没有什么是重启解决不了的方法修复的,本文的方法将会使用反射调用 WPF 的代码,我仅仅有测试 .NET Framework 4.8 的框架里面的逻辑,这就意味着需要你在运行的设备上安装有 .NET Framework 4.8 框架,但是对于运行的 WPF 没有任何限制,可以使用 .NET Framework 4.5 甚至是 .NET Framework 4.0 的版本。当然,本文方法对于 .NET Core 3.1 和 .NET 5 同样生效

本文的核心逻辑就是调用 WispLogic 的 RegisterHwndForInput 和 UnRegisterHwndForInput 来实现重启触摸,但是没有真的结束触摸线程,因此不够彻底。而我自己基于开源的 WPF 框架也定制了可以从触摸线程都重启的强力版本,当然了,这个版本非开源的版本

在使用本文的方法之前,请确定你对触摸有足够的了解

如果你对触摸的了解很少,那么我推荐你先看以下博客

​WPF 触摸屏应用需要了解的知识​

​浅谈 Windows 桌面端触摸架构演进​

​WPF 客户端开发需要知道的触摸失效问题​

对于 Win32 应用来说,如果应用的触摸失效了,可以的解决方法是重新注册一次触摸,或者重启应用。而在 WPF 中,没有公开的方法可以让咱重启注册触摸,但是使用非公开的方法可以调用到。关于在 WPF 中的触摸调用细节请看 ​​WPF 触摸到事件​​​ 和 ​​WPF 通过 InputManager 模拟调度触摸事件​

重启注册触摸的步骤就是先反注册,然后再次注册。分别调用的是 WispLogic 的 UnRegisterHwndForInput 和 RegisterHwndForInput 方法,以下是步骤

在 WPF 中,可以使用下面代码获取 StylusLogic 对象

在没有开启 Pointer 的情况下,这里的 StylusLogic 就是 WispLogic 对象,因为 WispLogic 的定义如下


internal class WispLogic : StylusLogic
{

}


在 Win10 中,大多数的触摸失效问题,都可以通过开启 Pointer 消息解决。而在 .NET 5 中,修复了 WPF 使用 WM_Pointer 消息在高 DPI 下的兼容触摸。如何开启 Pointer 消息请看 ​​WPF dotnet core 如何开启 Pointer 消息的支持​

在获取到 WispLogic 就可以通过反射调用 RegisterHwndForInput 和 UnRegisterHwndForInput 方法来重启触摸

通过开源的 WPF 代码可以看到两个方法的定义如下

internal void RegisterHwndForInput(InputManager inputManager, PresentationSource inputSource)
{

}

internal void UnRegisterHwndForInput(HwndSource hwndSource)
{

}


这里的 InputManager 可以使用 InputManager.Current 获取,而 PresentationSource 可以使用 ​​PresentationSource.FromVisual(this)​​​ 获取,上面的 ​​this​​ 需要一个在界面显示的元素

而 HwndSource 可以使用下面代码获取

var windowInteropHelper = new WindowInteropHelper(this);
var hwndSource = HwndSource.FromHwnd(windowInteropHelper.Handle);

先调用 UnRegisterHwndForInput 禁用触摸,然后调用 RegisterHwndForInput 打开触摸

object stylusLogic = GetStylusLogic();

if (stylusLogic == null)
{
return;
}

Type inputManagerType = typeof(System.Windows.Input.InputManager);
var wispLogicType = inputManagerType.Assembly.GetType("System.Windows.Input.StylusWisp.WispLogic");

var windowInteropHelper = new WindowInteropHelper(this);
var hwndSource = HwndSource.FromHwnd(windowInteropHelper.Handle);

var unRegisterHwndForInputMethodInfo = wispLogicType.GetMethod("UnRegisterHwndForInput",
BindingFlags.Instance | BindingFlags.NonPublic);

unRegisterHwndForInputMethodInfo.Invoke(stylusLogic, new object[] {hwndSource});


var registerHwndForInputMethodInfo = wispLogicType.GetMethod("RegisterHwndForInput",
BindingFlags.Instance | BindingFlags.NonPublic);

registerHwndForInputMethodInfo.Invoke(stylusLogic, new object[]
{
InputManager.Current,
PresentationSource.FromVisual(this)
});


以上代码放在 ​​github​​​ 和 ​​gitee​​ 欢迎小伙伴访问

本文的方法不能解决内部逻辑调用问题的触摸失效问题,也不能解决太过诡异的系统的触摸失效问题。本文的重启触摸的方法的执行速度是很慢的

以上方法也是有缺点的,使用了上面方法之后,就不能使用 ​​高性能 DynamicRenderer 书写​​ 的方式。解决 DynamicRenderer 丢失的方法就是重新注册一次 StylusPlugIn 元素