WPF 窗口触摸失效的常见场景及解决方案

156 阅读4分钟

前言

最近,生产线反馈,在执行生产大屏测试软件的时候,软件大概率出现不能触摸,但是可以用鼠标的的情况。刚好 这个软件又是WPF 做的,所以做了以下排查。

正文

.NET 环境: .NET FrameWork 4.8(经过测试,.NET 6 的版本也会出现)

建立一个最简单的Demo复现以上的问题,就是在一个主窗口MainWindow的Loaded事件,延时2s启动 Show 出 Window1 的空白窗口,间隔10s中,自动关闭

/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        btn.Background = Brushes.Red;
        Loaded += MainWindow_Loaded;
        StylusDown += MainWindow_StylusDown;
        MouseDown += MainWindow_MouseDown;
    }
 
    private void MainWindow_MouseDown(object sender, MouseButtonEventArgs e)
    {
        App.Log.Info("MainWindow_MouseDown");
    }
 
    private void MainWindow_StylusDown(object sender, StylusDownEventArgs e)
    {
        App.Log.Info("MainWindow_StylusDown");
    }
 
    private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        await Task.Delay(2000);
 
        var win = new Window1();
        win.Owner = this;
        win.Closed += Win_Closed;
        win.Show();
 
        await Task.Delay(10000);
        win.Close();
    }
 
    private void Win_Closed(object sender, EventArgs e)
    {
        grid1.Visibility = Visibility.Visible;
    }
 
    private void Btn_Click(object sender, RoutedEventArgs e)
    {
        App.Log.Info($"按钮点击了Btn_Click");
        if (btn.Background == Brushes.Green)
        {
            btn.Background = Brushes.Red;
        }
        else if (btn.Background == Brushes.Red)
        {
            btn.Background = Brushes.Green;
        }
    }
}
<Window
    x:Class="WpfApp1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:WpfApp1"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    WindowState="Maximized"
    mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="1*" />
            <RowDefinition Height="2*" />
        </Grid.RowDefinitions>
        <Grid x:Name="grid1" Visibility="Collapsed">
            <Button
                x:Name="btn"
                Width="200"
                Height="50"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Click="Btn_Click" />
        </Grid>
    </Grid>
</Window>
<Window
    x:Class="WpfApp1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:WpfApp1"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="Window1"
    Width="800"
    Height="450"
    Topmost="True"
    WindowState="Maximized"
    mc:Ignorable="d">
    <Grid />
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
 
namespace WpfApp1
{
    /// <summary>
    /// Window1.xaml 的交互逻辑
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }
    }
}

添加现场复现的操作手法:

在Show出Window1的时候,用双手的手掌按住Window1的窗口,直至10s后Window1窗口自动关闭,这时候MainWindow 窗口就不能触摸了。测试了几台规格不一样的大屏,均能复现到,只不过有些能自动恢复触摸的比较快(1分钟),有一台大屏则需要15分钟甚至更长的时间还不能恢复。

选择了一台久久不能恢复的大屏,用了德熙哥团队ManipulationDemo工具(详见:WPF 使用 ManipulationDemo 工具辅助调试设备触摸失效问题)协助排查,在ManipulationDemo加入了以上的弹窗的代码,没有异常数据,比如:插拔、程序Exception的抛出,没有任何触摸的Down、Move、Up的事件触发,但是是可以接受到WM_Pointer的消息的,并且接上鼠标,是有鼠标的Down、Move、Up 的事件触发

以上的代码,再次使用Dnspy运行调试进去,用相同的复现手法复现问题,查看 在 PresentationCore 程序集下的 Sysrem.Windows.Input 程序集下 PenThreadWorker 类里边,断点调试 ThreadProc 的方法体

发现如下问题:

1、_PenContexts 和 _handles 的个数都是2,这样会走进去执行 GetPenEventMultiple

2、成功执行了UnsafeNativeMethods.GetPenEventMultiple 的方法,返回的 cPackets、cbPacket、pPackets 都是 0

3、调用 FireEvent 的方法,array = null,导致不执行Down、Move、Up的方法了

正常的书写调试

用Dnspy,调试了正常书写流程的,子窗口Windows1 的关闭,是会调用RemovePenContext,释放到已经无效的 PenContext,handle,这样_PenContexts 和 _handles 的个数就是1,就会进入调用UnsafeNativeMethods.GetPenEvent,获取到正常的书写数据包,从而进入FirePenDown、FirePenUp 得到可书写的状态

目前的解决方案

1、由于在触摸失效的时候,是可以监听到Pointer的消息的, 可以在WPF 窗口开启Pointer的设置

AppContext.SetSwitch
(``"Switch.System.Windows.Input.Stylus.EnablePointerSupport"``, ``true``);

但是 开启Pointer 会有其他的问题,我测试过用.Net6版本,开启Pointer消息,确实能解决大部分触摸失效的问题,但是由于 Pointer的消息输出的是屏幕的坐标,所以应用内部还需要做适配,还会有其他的坑。

2、利用规避的方式,由于触摸窗口的自动关闭,导致触摸的PenContext数据错误的问题,先可以不将Windows1的窗口关闭,调用Window1.Hide() 的方式,隐藏起来,也可以规避当前的问题。

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

作者:wuty007

出处:cnblogs.com/wuty/p/18570445

声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!