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 的觸摸失效總結成三種不同的類型。
- 觸摸下 Stylus/Touch 事件正常觸發,但不提升為 Mouse 事件;導致僅使用 Mouse 事件的控件無法使用
- 觸摸下 Stylus/Touch 有觸發,但觸發點位置在 (0, 0) 處或上一個觸摸點處;導致即使觸發了,當前控件也收不到
- 觸摸下無 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 的疑難雜癥。
WPF 程序無法觸摸操作?我們一起來找原因和解決方法!