[WPF]為舊版本的應用新增觸控支援
之前做WPF開發時曾經遇到這樣一個需求:為一個基於 .NET Framework 3.5開發的老舊WPF程式新增觸控支援,以便於大屏觸控展示。
接手之後發現這是一個大坑。
專案最初的時候完全沒考慮過軟體架構設計,業務邏輯基本都寫在後臺程式碼中,經過兩代程式設計師的開發維護(初代開發者已離職,文件這種東西不存在的),主介面cs程式碼已經有上萬行,各種事件註冊的非常雜亂。由於是做給政府部門用的,穩定性很重要,修修補補不斷的打補丁,程式已經非常難維護了。
而且不像最新.net框架下的WPF以及UWP開發中,我們有Pointer開頭的系列事件可以統一處理滑鼠點選和觸控。在基於.net框架 4.7以下版本構建的WPF應用裡,滑鼠點選和觸控是獨立的,需要分別處理。
這裡有一點需要說明:在單點電阻式觸控屏(除了ATM機之類的特殊用途,基本要被淘汰掉了)下,系統對單點觸控的處理是模擬的滑鼠操作,這種情況下即使不處理觸控事件,程式也可以正常執行,需要處理觸控事件特指的是支援多點觸控的電容式觸控式螢幕。
當時我接手的WPF應用之前是完全沒有做過觸控事件處理的,我粗略的查詢統計了一下,需要處理的按鈕點選事件大概有上千個,如果手動處理,將是非常難以接受的重複工作,另外修改後的應用程式也必須完整走一遍測試流程,以防帶來災難性BUG。
那麼有沒有一種簡單的方法可以快速處理呢?
我們知道WPF開發中,所有的使用者互動事件都是路由事件,其中帶有Preview字首的為隧道路由事件,不帶字首的為冒泡路由事件。其區別是:隧道路由事件由根元素傳遞到觸發事件的元素,而冒泡路由事件傳遞方向正好相反。那麼,儘管程式中需要處理觸控事件的地方很多,但是我們都可以在應用頂層元素中通過冒泡路由事件攔截到。是不是可以利用這一點做文章呢?
我的想法是這樣的:由於應用已經處理了滑鼠互動事件,那我們完全可以將應用的觸控事件轉發給滑鼠互動事件的Handler去處理,這樣就避免了我們做機械的重複操作。
具體處理步驟如下:
-
在應用視窗的頂級元素(視覺化樹的根節點)上新增觸控事件處理程式,捕獲應用內部觸控事件;
this.AddHandler(TouchUpEvent, new RoutedEventHandler(GetTouchUp)); this.AddHandler(TouchDownEvent, new RoutedEventHandler(GetTouchDown));
-
獲取引發事件的源控制元件(原本想通過e.OriginalSource獲取,但測試中發現獲取的有錯誤,所以用UIElement類中的InputHitTest方法傳入觸控點座標,獲取到引發事件的源控制元件);
TouchEventArgs te = (TouchEventArgs)e; Point p = te.GetTouchPoint(this).Position;//這裡是獲取觸控點相對某個介面元素的座標 UIElement uiControl = (UIElement)this.InputHitTest(p);
-
觸發源控制元件的滑鼠事件(在TouchUp中還同時觸發了Button類的Click事件,用於處理按鈕的點選事件);
MouseButtonEventArgs args = new MouseButtonEventArgs(Mouse.PrimaryDevice,te.Timestamp,MouseButton.Left); args.RoutedEvent = MouseDownEvent; uiControl.RaiseEvent(args);
完整的事件處理程式碼如下:
this.AddHandler(TouchUpEvent, new RoutedEventHandler(GetTouchUp)); this.AddHandler(TouchDownEvent, new RoutedEventHandler(GetTouchDown)); private void GetTouchDown(object sender, RoutedEventArgs e) { TouchEventArgs te = (TouchEventArgs)e; Point p = te.GetTouchPoint(this).Position; UIElement uiControl = (UIElement)this.InputHitTest(p); MouseButtonEventArgs args = new MouseButtonEventArgs(Mouse.PrimaryDevice, te.Timestamp, MouseButton.Left); args.RoutedEvent = MouseDownEvent; uiControl.RaiseEvent(args); } private void GetTouchUp(object sender, RoutedEventArgs e) { TouchEventArgs te = (TouchEventArgs)e; Point p = te.GetTouchPoint(this).Position; UIElement uiControl = (UIElement)this.InputHitTest(p); MouseButtonEventArgs args = new MouseButtonEventArgs(Mouse.PrimaryDevice, te.Timestamp, MouseButton.Left); args.RoutedEvent = MouseUpEvent; uiControl.RaiseEvent(args); uiControl.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); }
要說明的一點是,我這裡的處理是不完善的,僅僅處理了常見的點選操作。譬如滑鼠右鍵(合理的觸控事件應該是長按介面元素一段時間後觸發),滑鼠移動,滾輪操作都沒有做處理,這些也是可以通過類似的方法轉換為合適的觸控事件觸發的。
結尾
今天文章裡所述的內容其實已經是很久以前的東西了,我現在的主要工作方向遠離WPF開發很久了,突然翻相關的舊檔案想起來,所以才有了這篇文章。好記性不如爛筆頭,知識不用總有忘的一天,不如寫出來貢獻給需要的人,謝謝大家!