在 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 模擬觸摸設備