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