Skip to content

WPF 触摸失效 试试重启触摸

Updated: at 08:22,Created: at 11:54

在使用一些诡异的系统以及诡异的触摸框的时候,也许会出现 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 对象

private object GetStylusLogic()
{
TabletDeviceCollection devices = System.Windows.Input.Tablet.TabletDevices;
if (devices.Count > 0)
{
// Get the Type of InputManager.
Type inputManagerType = typeof(System.Windows.Input.InputManager);
// Call the StylusLogic method on the InputManager.Current instance.
object stylusLogic = inputManagerType.InvokeMember("StylusLogic",
BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic,
null, InputManager.Current, null);
return stylusLogic;
}
return null;
}

在没有开启 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)
});

以上代码放在 githubgitee 欢迎小伙伴访问

更新版本的使用 WPF 最简逻辑实现多指顺滑的笔迹书写 的代码放在 githubgitee 欢迎小伙伴访问

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

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

更多触摸请看 WPF 触摸相关 更多笔迹相关请看


知识共享许可协议

原文链接: http://blog.lindexi.com/post/WPF-%E8%A7%A6%E6%91%B8%E5%A4%B1%E6%95%88-%E8%AF%95%E8%AF%95%E9%87%8D%E5%90%AF%E8%A7%A6%E6%91%B8

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。 欢迎转载、使用、重新发布,但务必保留文章署名 林德熙 (包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我 联系