最近,生產線反饋,在執行生產大屏測試軟體的時候,軟體大機率出現不能觸控,但是可以用滑鼠的的情況。剛好 這個軟體又是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() 的方式,隱藏起來,也可以規避當前的問題。