記錄一下 WPF 視窗 觸控失效 的一種場景

wuty007發表於2024-11-26

最近,生產線反饋,在執行生產大屏測試軟體的時候,軟體大機率出現不能觸控,但是可以用滑鼠的的情況。剛好 這個軟體又是WPF 做的,所以做了以下排查。

.Net 環境: .NetFrameWork 4.8(經過測試,.Net6的版本也會出現)

建立一個最簡單的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的訊息的

以上的程式碼,再次使用Dnsy執行除錯進去,用相同的復現手法復現問題,檢視 在 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的設定,(詳見冬哥的 WPF 開啟Pointer訊息 - 唐宋元明清2188 - 部落格園

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

但是 開啟Pointer 會有其他的問題,我測試過用.Net6版本,開啟Pointer訊息,確實能解決大部分觸控失效的問題,但是由於 Pointer的訊息輸出的是螢幕的座標,所以應用內部還需要做適配,還會有其他的坑

詳見德熙的 WPF 開啟Pointer訊息存在的坑 - lindexi - 部落格園

2、利用規避的方式,由於觸控視窗的自動關閉,導致觸控的PenContext資料錯誤的問題,先可以不將Windows1的視窗關閉,呼叫Window1.Hide() 的方式,隱藏起來,也可以規避當前的問題。

相關文章