天天看點

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

原文: 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

屬性為

null

NoMousePromotionStylusDevice

。此方法不執行會直接導緻

StylusLogic.ShouldPromoteToMouse

方法對目前觸控裝置的判斷出現錯誤,持續傳回

false

,即不會再執行觸控轉滑鼠的邏輯,出現觸摸無效的現象。

第二種情況

如果 WPF 的 StylusUp 事件被阻斷(例如

e.Handled = true

,或者在 StylusUp 事件中彈出一個模态視窗),則下一次觸摸時擷取到的點坐标将是上一次被阻斷時的點坐标。于是,阻斷後的第一次點選必将點中之前點的那個點,而不管現在點中了什麼。如果阻斷時點在新視窗外,則幾乎相當于觸摸失效。需要注意的是,這種情況下

MouseUp

e.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

中進行分發。在我的

Up

catch

所有異常,随後延遲引發。

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 事件。于是我們隻能要求多人開發項目中的每一位開發人員都注意不要在

StylusUp

e.Handled = true

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

Roslyn 入門:使用 Roslyn 靜态分析現有項目中的代碼

啟動時觸摸裝置擷取錯誤的問題我還沒有一個徹底的解決方案,目前是檢測第一次機會異常,并在發現錯誤堆棧是以上情況的時候重新啟動應用程式。能夠采取這樣的政策是因為此異常發生在我們的

App

類初始化之後

MainWindow

顯示出來之前。

更多的想法

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