WPF 經過 InputManager 模擬調度觸摸事件

在 WPF 中,框架能夠分爲兩個部分,一個是渲染,另外一個是交互。交互的入口是在 InputManager 裏面,而實際的交互實現須要經過渲染布局和交互的路由事件才能完成。在輸入管理提供了調度事件的方法,這個方法能夠被傳入路由事件,傳入的路由事件將會被調度到路由事件指定的元素上進行觸發。本文告訴你們如何模擬調度一個觸摸事件html

本文的內容屬於沒有任何官方文檔的支持的內容,如下是我看 WPF 源代碼瞭解到的用法git

在輸入管理裏面能夠經過 System.Windows.Input.InputManager.Current 拿到當前的輸入管理,這個屬性默認和 Dispatcher.CurrentDispatcher.InputManager 是相同的對象,只有在初始化的時候 Dispatcher.CurrentDispatcher.InputManager 會是空拿不到值,而經過 System.Windows.Input.InputManager.Current 將會自動建立github

此時就能夠回答這個 InputManager.Current 是針對進程仍是線程的問題了,請問 CurrentDispatcher 是針對進程仍是線程呢app

在拿到輸入管理,就能夠調用 ProcessInput 方法傳入一個 InputEventArgs 了,能夠傳入一個路由事件,此時路由事件將會加入觸發隊列,在調度方法的核心是經過 Stack _stagingArea 字段作到棧的方式的調度框架

/// <summary>
        ///     Synchronously processes the specified input.
        /// </summary>
        /// <remarks>
        ///     The specified input is processed by all of the filters and
        ///     monitors, and is finally dispatched to the appropriate
        ///     element as an input event.
        /// </remarks>
        /// <returns>
        ///     Whether or not any event generated as a consequence of this
        ///     event was handled.
        /// </returns>
        public bool ProcessInput(InputEventArgs input)
        {
//             VerifyAccess();

            if(input == null)
            {
                throw new ArgumentNullException("input");
            }

            // Push a marker indicating the portion of the staging area
            // that needs to be processed.
            PushMarker();

            // Push the input to be processed onto the staging area.
            PushInput(input, null);

            // Post a work item to continue processing the staging area
            // in case someone pushes a dispatcher frame in the middle
            // of input processing.
            RequestContinueProcessingStagingArea();

            // Now drain the staging area up to the marker we pushed.
            bool handled = ProcessStagingArea();
            return handled;
        }

上面代碼核心的邏輯是 ProcessStagingArea 方法工具

簡化的代碼應該和下面差很少佈局

while((item = PopInput()) != null)
 {
    // 忽略 Pre-Process 邏輯

    // Raise the input event being processed.
    InputEventArgs input = item.Input;

    // Some input events are explicitly associated with an element.  Those that are not are associated with the target of the input device for this event.
    // 有些輸入的元素是和輸入事件關聯的,此時和輸入設備沒有關係
    // 上面的註釋說的是先經過 input.Source 獲取和輸入事件關聯的元素,若是不能獲取到,那麼也許輸入元素是和輸入設備關聯的,嘗試從輸入設備獲取
    DependencyObject eventSource = input.Source as DependencyObject;

    if (eventSource == null)
    {
        eventSource = input.Device.Target as DependencyObject;
    }


    if (InputElement.IsUIElement(eventSource))
    {
        UIElement e = (UIElement)eventSource;

        e.RaiseEvent(input, true); // Call the "trusted" flavor of RaiseEvent. 
    }
    else if (InputElement.IsContentElement(eventSource))
    {
        ContentElement ce = (ContentElement)eventSource;

        ce.RaiseEvent(input, true);// Call the "trusted" flavor of RaiseEvent.
    }
    else if (InputElement.IsUIElement3D(eventSource))
    {
        UIElement3D e3D = (UIElement3D)eventSource;

        e3D.RaiseEvent(input, true); // Call the "trusted" flavor of RaiseEvent
    }    
 }

上面的 PopInput 方法以下post

internal StagingAreaInputItem PopInput()
        {
            object input = null;

            if(_stagingArea.Count > 0)
            {
                input = _stagingArea.Pop();
            }

            return input as StagingAreaInputItem;
        }

也就是本質上都是調用了元素的 RaiseEvent 方法,裏面沒有什麼判斷邏輯測試

按照上面的邏輯,咱能夠嘗試本身模擬觸發觸摸事件。不過建立一個 TouchEventArgs 仍是比較複雜的邏輯,須要用 WPF 模擬觸摸設備this

可是簡單的測試是能夠經過觸摸一下屏幕,保存觸摸事件的參數

private void OnTouchDown(object sender, TouchEventArgs e)
        {
            _lastEventArgs = e;
        }

        private TouchEventArgs _lastEventArgs;

下面嘗試在鼠標按下的時候觸發這個事件

private void OnMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.StylusDevice != null)
            {
            }
            else
            {
                System.Windows.Input.InputManager.Current.ProcessInput(_lastEventArgs);
            }
        }

在觸摸以後點擊鼠標,能夠看到鼠標點擊的時候一樣觸發了觸摸按下事件

那若是想要模擬觸發觸摸移動的事件呢?能夠嘗試修改 RoutedEvent 屬性

_lastEventArgs.RoutedEvent = PreviewTouchDownEvent;
System.Windows.Input.InputManager.Current.ProcessInput(_lastEventArgs);
_lastEventArgs.RoutedEvent = PreviewTouchMoveEvent;
System.Windows.Input.InputManager.Current.ProcessInput(_lastEventArgs);
_lastEventArgs.RoutedEvent = PreviewTouchUpEvent;
System.Windows.Input.InputManager.Current.ProcessInput(_lastEventArgs);

上面圖片是測試工具 ManipulationDemo 的顯示,這個工具會在事件觸發的時候修改對應事件顏色,也就是在鼠標點擊的時候觸發了觸摸的按下和移動和擡起

用這個方法就能夠從路由事件這一層調度事件

上面的代碼放在 GitHub 上,小夥伴打開代碼須要關注的是 OnMouseDown 方法的代碼

根據上面的源代碼能夠知道框架裏面其實也是調用了 RaiseEvent 方法,也就是不使用交互框架的調度本身觸發是否能夠?實際上也是能夠的

只須要將 System.Windows.Input.InputManager.Current.ProcessInput(_lastEventArgs) 替換爲 ((UIElement)_lastEventArgs.Source).RaiseEvent(_lastEventArgs) 請看代碼

_lastEventArgs.RoutedEvent = PreviewTouchDownEvent;
((UIElement)_lastEventArgs.Source).RaiseEvent(_lastEventArgs);
//System.Windows.Input.InputManager.Current.ProcessInput(_lastEventArgs);
_lastEventArgs.RoutedEvent = PreviewTouchMoveEvent;
((UIElement)_lastEventArgs.Source).RaiseEvent(_lastEventArgs);
//System.Windows.Input.InputManager.Current.ProcessInput(_lastEventArgs);
_lastEventArgs.RoutedEvent = PreviewTouchUpEvent;
((UIElement)_lastEventArgs.Source).RaiseEvent(_lastEventArgs);
//System.Windows.Input.InputManager.Current.ProcessInput(_lastEventArgs);

此時運行測試項目也能夠看到和 ProcessInput 同樣的效果

本文實際上是補充 WPF 觸摸到事件 的後半部分,從 WPF 觸摸到路由事件,是如何從觸摸事件讓對應的元素觸發

本文的方法僅是模擬事件的觸發,若是想要修改觸摸的點的座標等,須要本身實現 TouchDevice 類,請看 WPF 模擬觸摸設備

相關文章
相關標籤/搜索