WPF 触屏事件后触发鼠标事件的问题及 DataGrid 误触问题
WPF 觸屏事件后觸發鼠標事件的問題及 DataGrid 誤觸問題
目錄
一、觸屏事件連帶觸發鼠標事件的問題
二、DataGrid 誤觸問題及解決方法
獨立觀察員 2021 年 10 月 10 日
一、觸屏事件連帶觸發鼠標事件的問題
這個是?WPF?已知的問題,網絡上也有一些討論,但是沒有一個完美的方法來解決。本文也就是講解其中的一種方法,親測可行。
先來說說具體現象:觸屏操作時,如果程序里使用了觸屏事件(如:PreviewTouchDown、TouchDown、PreviewTouchUp、TouchUp),那么相應地會接著觸發鼠標事件(PreviewMouseDown、MouseDown、PreviewMouseUp、MouseUp),這個據說是微軟為了在觸屏設備上兼容老程序,讓這些程序能夠接收從觸屏事件轉換來的鼠標事件,從而能正常工作。
所以,有一個說法是,只使用鼠標事件就行了,比如就單單使用 PreviewMouseDown 事件,或者按鈕的話直接使用 Click 事件,或者使用命令(Command),這種方法理論上是可以的,但是實際情況下,有的時候會發現,這樣用的話,觸屏操作很不靈敏,可能要點好幾次才觸發。
這個觸屏事件提升為鼠標事件的一個表現就是,觸屏拖動或者點擊,會在屏幕上 “殘留” 鼠標,當然,是不可見的,或者表現為一個小星號。所以,從這個角度出發,產生了這樣一種方法:點擊后將鼠標移開。
這個方法能滿足部分場景,比如之前有這樣一個問題,在?DataGrid?表格上方有一個 DatePicker 日期選擇控件,日期展開后,下拉的懸浮框會遮在表格上,當在下拉的懸浮框中選擇日期后下拉框收起,這時卻在表格上產生了某個條目的選中效果。針對于這個情況,就可以使用移開鼠標的方案,相關幫助類見下方鏈接:
https://gitee.com/dlgcy/WPFTemplateLib/blob/master/WpfHelpers/ClickAndTouchHelper2.cs?
但是這次我遇到了一個 DataGrid 的誤觸問題,用移開鼠標的方法無效(也有可能是使用方法和時機不對),所以只能另尋它法。
注意,本文將在上篇文章《WPF DataGrid 通過自定義表頭模擬首行固定》的示例程序基礎上進行演示,建議先看看那篇文章。下面開始改造。
首先在行樣式中添加了兩個事件,一個是 PreviewTouchDown,另一個是 PreviewMouseDown:
觸屏點擊某一行,會先觸發 PreviewTouchDown,然后觸發 PreviewMouseDown,然后是行改變事件 SelectionChanged,最后依次是 PreviewTouchUp 和 PreviewMouseUp。帶有 Preview 前綴的是隧道事件(可視為在事件前觸發),沒有的是冒泡事件(可視為在事件后觸發,此處省略)。
那么如何去除觸屏事件后連帶引發鼠標事件的影響呢?通過在網絡上苦苦搜索和嘗試,在舊版的微軟社區找到了一個可行的方法,帖子為《Prevent a WPF application to interpret touch events as mouse events?》(這個鏈接之后可能會訪問不了)。
提問者就是為了解決觸屏操作下觸發鼠標事件的問題:
然后里面兩個人分別給出了他們的解決方法,先來看看第一個:
這個就是本文采納的方法,代碼文字版如下:
public static class PreventTouchToMousePromotion {public static void Register(FrameworkElement root){root.PreviewMouseDown = Evaluate;root.PreviewMouseMove = Evaluate;root.PreviewMouseUp = Evaluate;}private static void Evaluate(object sender, MouseEventArgs e){//StylusDevice 屬性,觸屏操作連帶觸發時不為 null,鼠標觸發時為 null;if (e.StylusDevice != null){e.Handled = true; // 如果判斷為 由觸屏引發,則將事件標記為已處理;}} }再順便看看第二個人的方法(沒有去嘗試,感興趣的朋友可以試試):
二、DataGrid 誤觸問題及解決方法
上一個部分介紹了去除觸屏事件后連帶引發鼠標事件影響的方法,也就是通過鼠標事件參數的 StylusDevice 屬性來判斷是否是由觸屏操作引發的(不為 null 則是觸屏操作引發),進而進行處理。
然而,本次我實際上是要解決一個 DataGrid 表格在觸屏下的誤觸問題,相關業務邏輯是在行改變事件(轉為命令了)中的,本來是沒有寫 PreviewTouchDown 和 PreviewMouseDown 事件的(就是為了解決誤觸問題而引入),所以將鼠標事件標記為已處理(e.Handled = true;)的方法不能直接使用,還需要修改。原因是,行改變事件 SelectionChanged 是在 PreviewMouseDown 事件之后觸發的,如果在 PreviewMouseDown 中將事件標記為已處理,那么行改變事件也就不會觸發了。
首先來看看誤觸現象吧(動圖):
也就是,我在行改變事件中加了個彈窗,詢問用戶是否要切換條目,如果選是的話,不作任何處理,如果選否的話,恢復之前的選中項。選是的時候不會有誤觸現象,選否的時候,鼠標操作的話也正常,而如果在彈窗時通過觸屏點擊了否,然后在界面空白處(這里是在右側的信息區)觸屏點擊幾下,就會在表格上,在之前點擊要切換到的那一行上產生一個鼠標事件,而且沒有觸屏事件,這個不用懷疑,通過調試打斷點很容易觀察到。
關于點擊幾下會觸發這個誤觸,我發現和屏幕支持幾點觸控有關。比如,公司的觸摸屏支持 10 點觸控,那么這里就是點擊 10 下左右觸發;我自己的一個小觸摸屏,支持 5 點觸控,這邊則是在空白處點擊 4 下觸發。要查看屏幕支持幾點觸屏,可通過 GitHub 上的一個項目程序 ManipulationDemo 來查看(https://github.com/dotnet-campus/ManipulationDemo):
言歸正傳,從誤觸現象的動圖中可以看到,已經能夠判斷出是否是誤觸了:
那么是怎么判斷的呢?來看看代碼:
private void EventSetter_PreviewTouchDown(object sender, TouchEventArgs e) {// 真實觸摸時會觸發 PreviewTouchDown 事件,而誤觸時(點擊彈窗取消后在空白處點擊多次會誤觸表格)則不會(因為那個只觸發鼠標事件);_vm.IsRealTouch = true; }/* 注意:觸摸事件之后還會觸發鼠標事件 */private void EventSetter_PreviewMouseDown(object sender, MouseButtonEventArgs e) {//StylusDevice 屬性,觸屏操作連帶觸發時不為 null,鼠標觸發時為 nullif (e.StylusDevice != null){// 觸屏//e.Handled = true;}else{// 鼠標_vm.IsRealTouch = true; // 避免后續判斷不正常;} }在 ViewModel 中新增了一個標記變量 IsRealTouch,用來記錄是真實的觸控或者鼠標點擊意圖,還是誤觸。真實觸摸時會觸發 PreviewTouchDown 事件,而誤觸時(點擊彈窗取消后在空白處點擊多次會誤觸表格)則不會(因為那個只觸發鼠標事件),所以只要在鼠標事件 PreviewMouseDown 中能夠判斷出是否是觸屏操作連帶觸發的就行了,而這個問題在前一部分已經解決了。所以,在觸摸事件,以及鼠標事件的單純鼠標觸發的情況下,都對 IsRealTouch 賦值為 true 即可。
行改變事件(命令)中還需要給 IsRealTouch 復位,代碼如下:
SelectionChangedCmd ??= new RelayCommand(o => IsCanSelectionChanged, o => {try{IsCanSelectionChanged = false;var args = o as SelectionChangedEventArgs;EditType = EditTypeEnum.Show;var isOk = MessageBox.Show($" 是否切換?(是否是誤觸?{!IsRealTouch})", "觸屏誤觸問題演示", MessageBoxButton.YesNo);if (isOk == MessageBoxResult.No){if (SelectedUser != _originUser){SelectedUser = _originUser;}}}catch (Exception ex){Console.WriteLine(ex);}finally{IsRealTouch = false;_originUser = SelectedUser;IsCanSelectionChanged = true;} });可以看到,這樣就能識別出是否是誤觸了。這里是演示,在實際使用時,識別到是誤觸,就可以直接返回而不用彈窗了。
問題解決了,那么原因呢?對于觸屏操作產生鼠標事件,這個是微軟為了兼容性而導致的,前面也說過了。至于為什么會有個觸點殘留在原來的位置,而且點擊其它地方一定次數就會觸發,這個問題我也沒找到原因,請知道的朋友不吝賜教。有兩個猜測,一是模態彈窗對事件有影響,一是命令對事件有影響,目前沒想到怎么驗證。
另外,之前說過彈窗點擊是的情況下,后續沒有誤觸現象,所以也有理由懷疑是從代碼中改變了選中項(已綁定到 DataGrid 的選中項)所以會有這個問題。從代碼中改變選中項又會觸發行改變事件,所以加了個 IsCanSelectionChanged 來避免重入,當然,加不加這個避免重入的,都有誤觸現象。有點暈。?
最后奉上源碼地址:https://gitee.com/dlgcy/DLGCY_WPFPractice/tree/Blog20211010?,大家可以幫忙研究研究。
總結
以上是生活随笔為你收集整理的WPF 触屏事件后触发鼠标事件的问题及 DataGrid 误触问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 活久见啊,WPF工资已经这么高了!
- 下一篇: 最新.NET MAUI有什么惊喜?