1. 程式人生 > >WPF 程序無法觸摸操作?我們一起來找原因和解決方法!

WPF 程序無法觸摸操作?我們一起來找原因和解決方法!

line 坐標 團隊 github webkit pro rgba alter 徹底

原文:WPF 程序無法觸摸操作?我們一起來找原因和解決方法!

版權聲明:本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名呂毅(包含鏈接:http://blog.csdn.net/wpwalter/),不得用於商業目的,基於本文修改後的作品務必以相同的許可發布。如有任何疑問,請與我聯系([email protected])。 https://blog.csdn.net/WPwalter/article/details/77986954

WPF 自誕生以來就帶著微軟先生的傲慢。微軟說 WPF 支持觸摸,於是 WPF 就真的支持觸摸了。對,我說的是“支持觸摸”,那種摸上去能點能動的;偶爾還能帶點兒多指的炫酷效果。但是,WPF 推出那會兒,絕大部分開發者都還沒有觸摸屏呢,開發個程序要怎麽驗證支不支持觸摸呢?微軟先生無奈地決定——你寫鼠標的代碼就好了,我幫你轉換!於是……一大波 BUG 襲來……


WPF 觸摸失效的分類

我將 WPF 的觸摸失效總結成三種不同的類型。

  1. 觸摸下 Stylus/Touch 事件正常觸發,但不提升為 Mouse 事件;導致僅使用 Mouse 事件的控件無法使用
  2. 觸摸下 Stylus/Touch 有觸發,但觸發點位置在 (0, 0) 處或上一個觸摸點處;導致即使觸發了,當前控件也收不到
  3. 觸摸下無 Stylus/Touch 事件,也不提升為 Mouse 事件,但鼠標下有 Mouse 事件;導致整個界面完全無法觸摸使用

第一種情況

使用觸摸或者觸筆操作時,如果 Up 事件中發生了任何異常,會導致 StylusLogic.PostProcessInput 的後續邏輯不會正確執行,這就包括了用於清理觸控資源的 StylusTouchDevice.OnDeactivate 方法。需要註意的是:Up 事件不止是 TouchUp 或者 StylusUp

MouseUp 也會引發這樣的觸摸失效。

而在 StylusTouchDevice.OnDeactivate 方法中,會重置 StylusLogic.CurrentMousePromotionStylusDevice 屬性為 nullNoMousePromotionStylusDevice。此方法不執行會直接導致 StylusLogic.ShouldPromoteToMouse 方法對當前觸控設備的判斷出現錯誤,持續返回 false,即不會再執行觸控轉鼠標的邏輯,出現觸摸無效的現象。

第二種情況

如果 WPF 的 StylusUp 事件被阻斷(例如 e.Handled = true,或者在 StylusUp 事件中彈出一個模態窗口),則下一次觸摸時獲取到的點坐標將是上一次被阻斷時的點坐標。於是,阻斷後的第一次點擊必將點中之前點的那個點,而不管現在點中了什麽。如果阻斷時點在新窗口外,則幾乎相當於觸摸失效。需要註意的是,這種情況下 MouseUpe.Handled = true 是可以使用而不會導致觸摸失效的。

第三種情況

WPF 程序在啟動期間,如果觸摸組件發生了異常,極有可能會使得觸摸根本就沒有初始化成功!

比如,System.Windows.Input.StylusLogic.RegisterStylusDeviceCore(StylusDevice stylusDevice) 方法在啟動時拋出 System.InvalidOperationException,雖然內部有 catch,但實際獲取到的 TabletDevice 個數是 0 個,根本無法獲取觸摸設備,於是觸摸無效。

或者,在 WorkerOperationGetTabletsInfo.OnDoWork 方法中,獲取到了錯誤的觸摸設備個數:

IPimcManager pimcManager = UnsafeNativeMethods.PimcManager;
uint count;
pimcManager.GetTabletCount(out count);

解決之道

目前為止,這三種問題都沒有根本的解決辦法,但是我們可以規避。

第一種情況

我們沒有辦法阻止每一處的 Up 事件,所以我的做法是在禁止那些可能會在 Up 中引發異常的操作監聽 Up 事件,而是統一由我封裝好的 Down/Move/Up 中進行分發。在我的 Upcatch 所有異常,隨後延遲引發。

try
{
    // 分發真正業務上的 Up 事件。
    DeliverUpEvent(e);
}
catch (Exception ex)
{
    // 使用觸摸或者觸筆操作時,如果 Up 事件中發生了任何異常,會導致 StylusLogic.PostProcessInput 的後續邏輯不會正確執行,
    // 這就包括了用於清理觸控資源的 StylusTouchDevice.OnDeactivate 方法。
    // 
    // 而在 StylusTouchDevice.OnDeactivate 方法中,會重置 StylusLogic.CurrentMousePromotionStylusDevice 屬性
    // 為 null 或 NoMousePromotionStylusDevice。此方法不執行會直接導致 StylusLogic.ShouldPromoteToMouse 方法
    // 對當前觸控設備的判斷出現錯誤,持續返回 false,即不會再執行觸控轉鼠標的邏輯,出現觸摸無效的現象。
    // 
    // 這裏通過 InvokeAsync 的方式再次拋出異常是為了在保證 Stylus 邏輯不出錯的情況下,將異常暴露。
    Dispatcher.CurrentDispatcher.InvokeAsync(() =>
    {
        ExceptionDispatchInfo.Capture(ex).Throw();
    });
}

第二種情況

一樣的,我們沒有辦法阻止每一處的 Up 事件。於是我們只能要求多人開發項目中的每一位開發人員都註意不要在 StylusUpe.Handled = true

然而,要求每一個人都這麽做是不現實的,尤其是團隊成員不穩定的情況下。目前我還沒有找到具體可實施的自動化的解決辦法,不過我最近正在嘗試的 Roslyn 擴展可能可以解決這樣的問題。有關 Roslyn 擴展的開發,可以閱讀我的另一篇文章:Roslyn 入門:使用 Roslyn 靜態分析現有項目中的代碼。

第三種情況

啟動時觸摸設備獲取錯誤的問題我還沒有一個徹底的解決方案,目前是檢測第一次機會異常,並在發現錯誤堆棧是以上情況的時候重新啟動應用程序。能夠采取這樣的策略是因為此異常發生在我們的 App 類初始化之後 MainWindow 顯示出來之前。

更多的想法

期待你有更多的想法,我希望在我們的交流之下,能夠幫助更多人發現和解決 WPF 的觸摸失效問題,甚至更多 WPF 的疑難雜癥。

WPF 程序無法觸摸操作?我們一起來找原因和解決方法!