1. 程式人生 > >[WPF]為舊版本的應用新增觸控支援

[WPF]為舊版本的應用新增觸控支援

之前做WPF開發時曾經遇到這樣一個需求:為一個基於 .NET Framework 3.5開發的老舊WPF程式新增觸控支援,以便於大屏觸控展示。

接手之後發現這是一個大坑。

專案最初的時候完全沒考慮過軟體架構設計,業務邏輯基本都寫在後臺程式碼中,經過兩代程式設計師的開發維護(初代開發者已離職,文件這種東西不存在的),主介面cs程式碼已經有上萬行,各種事件註冊的非常雜亂。由於是做給政府部門用的,穩定性很重要,修修補補不斷的打補丁,程式已經非常難維護了。

而且不像最新.net框架下的WPF以及UWP開發中,我們有Pointer開頭的系列事件可以統一處理滑鼠點選和觸控。在基於.net框架 4.7以下版本構建的WPF應用裡,滑鼠點選和觸控是獨立的,需要分別處理。

這裡有一點需要說明:在單點電阻式觸控屏(除了ATM機之類的特殊用途,基本要被淘汰掉了)下,系統對單點觸控的處理是模擬的滑鼠操作,這種情況下即使不處理觸控事件,程式也可以正常執行,需要處理觸控事件特指的是支援多點觸控的電容式觸控式螢幕。

當時我接手的WPF應用之前是完全沒有做過觸控事件處理的,我粗略的查詢統計了一下,需要處理的按鈕點選事件大概有上千個,如果手動處理,將是非常難以接受的重複工作,另外修改後的應用程式也必須完整走一遍測試流程,以防帶來災難性BUG。

那麼有沒有一種簡單的方法可以快速處理呢?

我們知道WPF開發中,所有的使用者互動事件都是路由事件,其中帶有Preview字首的為隧道路由事件,不帶字首的為冒泡路由事件。其區別是:隧道路由事件由根元素傳遞到觸發事件的元素,而冒泡路由事件傳遞方向正好相反。那麼,儘管程式中需要處理觸控事件的地方很多,但是我們都可以在應用頂層元素中通過冒泡路由事件攔截到。是不是可以利用這一點做文章呢?

我的想法是這樣的:由於應用已經處理了滑鼠互動事件,那我們完全可以將應用的觸控事件轉發給滑鼠互動事件的Handler去處理,這樣就避免了我們做機械的重複操作。

具體處理步驟如下:

  1. 在應用視窗的頂級元素(視覺化樹的根節點)上新增觸控事件處理程式,捕獲應用內部觸控事件;

    this.AddHandler(TouchUpEvent, new RoutedEventHandler(GetTouchUp));
    this.AddHandler(TouchDownEvent, new RoutedEventHandler(GetTouchDown));
  2. 獲取引發事件的源控制元件(原本想通過e.OriginalSource獲取,但測試中發現獲取的有錯誤,所以用UIElement類中的InputHitTest方法傳入觸控點座標,獲取到引發事件的源控制元件);

    TouchEventArgs te = (TouchEventArgs)e;
    Point p = te.GetTouchPoint(this).Position;//這裡是獲取觸控點相對某個介面元素的座標
    UIElement uiControl = (UIElement)this.InputHitTest(p);
  3. 觸發源控制元件的滑鼠事件(在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開發很久了,突然翻相關的舊檔案想起來,所以才有了這篇文章。好記性不如爛筆頭,知識不用總有忘的一天,不如寫出來貢獻給需要的人,謝謝大家!