在 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 模拟触摸设备