原文:2019-11-29-WPF-多个-StylusPlugIn-的事件触发顺序

title author date CreateTime categories
WPF 多个 StylusPlugIn 的事件触发顺序
lindexi
2019-11-29 10:20:41 +0800
2019-10-19 09:18:35 +0800
WPF

如果在 WPF 使用 StylusPlugIn 同时在同一个界面用多个元素都加上 StylusPlugIn 那么事件触发的顺序将会很乱
我建议是不要让 StylusPlugIn 有重叠,在没有理解 StylusPlugIn 之前请不要写出让 StylusPlugIn 有重叠的代码。因为可能有小伙伴移动了一个元素就让你的代码的行为和之前写的不一样

如果多个 StylusPlugIn 附加的元素没有重叠,那么所有元素的工作都会符合预期。也就是点到哪个元素,将会触发对应元素的 StylusPlugIn 方法

因为本文比较复杂,主要是很无聊的原理,所以只想了解现象的小伙伴只看下面图片就可以

我将会使用两个不同的框代表不同的元素,红色的框代表的是普通的容器,而蓝色代表附加StylusPlugIn元素

对同容器内两个重叠元素,将会同时触发两个元素的 StylusPlugIn 事件,不同的是在最底层的元素将会在触摸线程触发,而在最上层的元素将会是主线程触发

对同容器内多个重叠元素,将知道最上层和最底层的元素会触发事件,不同的是在最底层的元素将会在触摸线程触发,而在最上层的元素将会是主线程触发

如果是一个附加 StylusPlugIn 的容器,包含一个附加 StylusPlugIn 的元素,那么只有元素会触发在触摸线程触发事件

代码放在 github 建议下载代码测试

如果不想了解原理,请关闭页面

在阅读本文之前,请先看WPF 高速书写 StylusPlugIn 原理

如果多个元素有重叠,那么就需要分为以下不同的重叠方法

同容器内两个重叠元素

先定义一个自定义控件和一个 StylusPlugIn1 类

    public class CustomControl : Grid
{
public CustomControl()
{
Background = Brushes.White;
} public StylusPlugInCollection StylusPlugInCollection => StylusPlugIns;
} public class StylusPlugIn1 : StylusPlugIn
{
public StylusPlugIn1()
{
} public string Name { set; get; } protected override void OnStylusDown(RawStylusInput rawStylusInput)
{
Debug.WriteLine($"{Name} Down 当前线程{Thread.CurrentThread.Name} id={Thread.CurrentThread.ManagedThreadId}");
base.OnStylusDown(rawStylusInput);
} public override string ToString()
{
return $"{Name}";
}
}

在界面创建两个 CustomControl 元素加入到相同一个 Grid 作为元素

    <Grid>
<local:CustomControl x:Name="Control1">
<local:CustomControl.StylusPlugInCollection>
<local:StylusPlugIn1 Name="Stylus 1"></local:StylusPlugIn1>
</local:CustomControl.StylusPlugInCollection>
</local:CustomControl>
<local:CustomControl x:Name="Control2" >
<local:CustomControl.StylusPlugInCollection>
<local:StylusPlugIn1 Name="Stylus 2"></local:StylusPlugIn1>
</local:CustomControl.StylusPlugInCollection>
</local:CustomControl>
</Grid>

此时尝试触摸一下屏幕,可以看到下面输出

Stylus 1 Down 当前线程Stylus Input id=3
Stylus 2 Down 当前线程 id=1

也就是传入的 Stylus 1Stylus 2 都收到了 Down 但是分别是通过不同的线程传入

这里有一点疑惑是为什么 Control2 的界面层级比 Control1 的高,但是为什么反而是 Stylus 1 先收到按下

WPF 高速书写 StylusPlugIn 原理有说到一点原理,但是在这篇博客我不想在这个方面讲细节,所以将细节放在这篇博客

在 PenContexts.HittestPlugInCollection 方法,将会返回一个 StylusPlugInCollection 用于决定处理 StylusInput 线程的逻辑,而这个方法的代码如下

		private StylusPlugInCollection HittestPlugInCollection(Point pt)
{
foreach (StylusPlugInCollection stylusPlugInCollection in this._plugInCollectionList)
{
if (stylusPlugInCollection.IsHit(pt))
{
return stylusPlugInCollection;
}
}
return null;
}

这里的 _plugInCollectionList 就是全局添加到元素的 StylusPlugInCollection 列表,从上面代码可以看到没有做任何的排序,也就是拿到第一个可以命中的元素就返回。而这个字段的添加是依赖于视觉树添加的顺序,这也就是本文开始告诉大家的,不要做出重叠的原因

关于 _plugInCollectionList 字段是如何添加的,将会在下文说到,现在回到开始的问题

在触摸线程 StylusInput 线程拿到的 StylusPlugInCollection 是第一个满足条件的,而刚好按照视觉树是 Control1 先添加到视觉树,所以返回的就是第一个元素

在第一个元素返回之后,触发了 Down 就完成了触摸线程的逻辑了。而 Control2 是如何触发的?

请看 Control2 的调用堆栈

 	PresentationCore.dll!System.Windows.Input.StylusPlugIns.StylusPlugInCollection.FireRawStylusInput.AnonymousMethod__0()
PresentationCore.dll!System.Windows.Input.StylusPlugIns.StylusPlugInCollection.ExecuteWithPotentialLock(System.Action action)
PresentationCore.dll!System.Windows.Input.StylusPlugIns.StylusPlugInCollection.FireRawStylusInput(System.Windows.Input.StylusPlugIns.RawStylusInput args)
PresentationCore.dll!System.Windows.Input.StylusWisp.WispLogic.VerifyStylusPlugInCollectionTarget(System.Windows.Input.RawStylusInputReport rawStylusInputReport)
PresentationCore.dll!System.Windows.Input.StylusWisp.WispLogic.PreNotifyInput(object sender, System.Windows.Input.NotifyInputEventArgs e)
PresentationCore.dll!System.Windows.Input.InputManager.ProcessStagingArea()
PresentationCore.dll!System.Windows.Input.InputManager.ProcessInput(System.Windows.Input.InputEventArgs input)
PresentationCore.dll!System.Windows.Input.StylusWisp.WispLogic.InputManagerProcessInput(object oInput)

可以从上面堆栈看到这是主线程的调用堆栈,通过上面的输出可以看到这个方法是在主线程触发

WispLogic.VerifyStylusPlugInCollectionTarget 方法将调用触摸命中的元素的方法

            UIElement newTarget = InputElement.GetContainingUIElement(rawStylusInputReport.StylusDevice.DirectlyOver as DependencyObject) as UIElement;
if (newTarget != null)
{
targetPIC = rawStylusInputReport.PenContext.Contexts.FindPlugInCollection(newTarget);
}

现在 WPF 开放源代码了,以上代码在 WispLogic.cs#L2638 可以看到在找到 newTarget 的时候将会调用 FindPlugInCollection 方法寻找

而 targetPIC 的定义如下

            // See if we have a plugin for the target of this input.
StylusPlugInCollection targetPIC = null;

也就是在当前命中的是元素,同时在这个元素可以找到 StylusPlugInCollection 那么将给这个变量赋值

调用代码请看代码

               // Now fire RawStylusInput if needed to the right plugincollection.
if (sendRawStylusInput)
{
// We are on the pen thread, just call directly.
targetPIC.FireRawStylusInput(rawStylusInputReport.RawStylusInput);
updateEventPoints = (updateEventPoints || rawStylusInputReport.RawStylusInput.StylusPointsModified); // Indicate we've used a stylus plugin
Statistics.FeaturesUsed |= StylusTraceLogger.FeatureFlags.StylusPluginsUsed;
}

所以将会看到有 Stylus 1Stylus 2 的 Down 都被调用,但是不同的是 Stylus 2 是在主线程调用

同容器内多个重叠元素

在上面告诉大家同容器内两个重叠元素将会都触发事件

但是千万不要认为多个重叠的元素都会被触发,其实只有最先加入视觉树的元素和命中到的元素会触发,如下面代码

       <local:CustomControl x:Name="Control1">
<local:CustomControl.StylusPlugInCollection>
<local:StylusPlugIn1 Name="Stylus 1"></local:StylusPlugIn1>
</local:CustomControl.StylusPlugInCollection>
</local:CustomControl>
<local:CustomControl x:Name="Control2" >
<local:CustomControl.StylusPlugInCollection>
<local:StylusPlugIn1 Name="Stylus 2"></local:StylusPlugIn1>
</local:CustomControl.StylusPlugInCollection>
</local:CustomControl>
<local:CustomControl x:Name="Control3" >
<local:CustomControl.StylusPlugInCollection>
<local:StylusPlugIn1 Name="Stylus 3"></local:StylusPlugIn1>
</local:CustomControl.StylusPlugInCollection>
</local:CustomControl>

如果理解了上面的逻辑可以知道,第一个元素将会在触摸线程调用,而最后一个元素在主线程命中测试找到也会被调用,那么第二个元素呢

其实第二个元素因为没有在主线程命中测试找到,所以就不会被调用,上面代码在触摸屏幕可以看到下面代码

Stylus 1 Down 当前线程Stylus Input id=3
Stylus 3 Down 当前线程 id=1

容器和包含一个元素

如果容器本身就附加了 StylusPlugIn 同时容器里面也包含一个附加的元素,如下面代码,那么触发效果是什么

        <local:CustomControl x:Name="Control1">
<local:CustomControl.StylusPlugInCollection>
<local:StylusPlugIn1 Name="Stylus 1" />
</local:CustomControl.StylusPlugInCollection>
<local:CustomControl.Children>
<local:CustomControl x:Name="Control2">
<local:CustomControl.StylusPlugInCollection>
<local:StylusPlugIn1 Name="Stylus 2" />
</local:CustomControl.StylusPlugInCollection>
</local:CustomControl>
</local:CustomControl.Children>
</local:CustomControl>

通过运行代码可以看到输出的只有 Control2 事件

PenContexts.AddStylusPlugInCollection 方法会将当前元素的 StylusPlugIn 添加到全局的字段,而添加的时候会调用 PenContexts.FindZOrderIndex 方法,在这个方法将会决定添加的 StylusPlugIn 所在字段的顺序,因为在通过命中测试获取点击到的元素是按照字段列表的顺序获取,返回第一个满足的元素。在字段列表的顺序将会决定哪个元素响应

在 FindZOrderIndex 将会让 Control2 添加到最前,也就是在触摸线程命中测试将会返回 Control2 触发,而在主线程命中测试也是返回第二个控件

所以第一个控件没有被触发事件

2019-11-29-WPF-多个-StylusPlugIn-的事件触发顺序的更多相关文章

  1. 2019.11.29 Mysql的数据操作

    为名为name的表增加数据(插入所有字段) insert into name values(1,‘张三’,‘男’,20); 为名为name的表增加数据(插入部分字段) insert into name ...

  2. 2019.11.29 SAP SMTP郵件服務器配置 發送端 QQ郵箱

    今天群裏的小夥伴問了如何配置郵件的問題,隨自己在sap裏面配置了一個 1.    RZ10配置參數 a)       参数配置前,先导入激活版本 执行完毕后返回 b)      输入参数文件DEFAU ...

  3. pycharm+anaconda在Mac上的配置方法 2019.11.29

    内心os: 听人说,写blog是加分项,那他就不是浪费时间的事儿了呗 毕竟自己菜还是留下来东西来自己欣赏吧 Mac小电脑上进行python数据开发环境的配置 首先下载Anaconda,一个超好用的数据 ...

  4. Supervision meeting notes 2019/11/29

    topic 分支:  1. subgraph/subsequence mining Wang Jin, routine behavior/ motif. Philippe Fournier Viger ...

  5. EOJ Monthly 2019.11 E. 数学题(莫比乌斯反演+杜教筛+拉格朗日插值)

    传送门 题意: 统计\(k\)元组个数\((a_1,a_2,\cdots,a_n),1\leq a_i\leq n\)使得\(gcd(a_1,a_2,\cdots,a_k,n)=1\). 定义\(f( ...

  6. 黑盒测试实践--Day5 11.29

    黑盒测试实践--Day5 11.29 今天完成任务情况: 分析系统需求,完成场景用例设计 小组负责测试的同学学习安装自动测试工具--QTP,并在线学习操作 小黄 今天的任务是完成场景测试用例的设计.在 ...

  7. WPF 高速书写 StylusPlugIn 原理

    原文:WPF 高速书写 StylusPlugIn 原理 本文告诉大家 WPF 的 StylusPlugIn 为什么能做高性能书写,在我的上一篇博客和大家介绍了 WPF 的触摸原理,但是没有详细告诉大家 ...

  8. Alpha冲刺(6/10)——2019.4.29

    所属课程 软件工程1916|W(福州大学) 作业要求 Alpha冲刺(6/10)--2019.4.29 团队名称 待就业六人组 1.团队信息 团队名称:待就业六人组 团队描述:同舟共济扬帆起,乘风破浪 ...

  9. [New!!!]欢迎大佬光临本蒟蒻的博客(2019.11.27更新)

    更新于2019.12.22 本蒟蒻在博客园安家啦!!! 本蒟蒻的博客园主页 为更好管理博客,本蒟蒻从今天开始,正式转入博客园. 因为一些原因,我的CSDN博客将彻底不会使用!!!(带来不便,敬请谅解) ...

  10. 2019.11.9 csp-s 考前模拟

    2019.11.9 csp-s 考前模拟 是自闭少女lz /lb(泪奔 T1 我可能(呸,一定是唯一一个把这个题写炸了的人 题外话: 我可能是一个面向数据编程选手 作为一个唯一一个写炸T1的人,成功通 ...

随机推荐

  1. skipped obstructing working copy

    svn update时报错,处理方法,将报错的文件夹压缩备份一下,然后删除报错的文件夹,重新update即可.

  2. Mysql之架构篇

    1.主从复制解决方案 这是MySQL自身提供的一种高可用解决方案,数据同步方法采用的是MySQL replication技术.MySQL replication就是从服务器到主服务器拉取二进制日志文件 ...

  3. jenkins使用小技巧:pom.xml文件里的版本随着每次发布变化怎么办?

    针对这个问题,构建方法不变, 变化在动态去获取每次打出来的包名, 比如说,本次打出来的报名mypackage-1.0.3-SNAPSHOT.jar 那么,先进入target目录 #先进入target目 ...

  4. conan使用(二)--创建私有仓库

    前面我们已经能够使用conan来从公共服务器上拉取C/C++包来集成进我的工程中,但是在实际开发中,我们可能需要自己封装或使用非公开的库,那么自己搭建一个私服是个很现实的需求. 搭建conan私服有几 ...

  5. 201871010109-胡欢欢 《面向对象程序设计(java)》第十五周学习总结

    正文开头: 项目 内容 这个作业属于哪个课程 https://home.cnblogs.com/u/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.com/nwn ...

  6. web-程序逻辑问题

    题目 查看源码 http://ctf5.shiyanbar.com/web/5/index.txt 代码如下 <html> <head> welcome to simplexu ...

  7. 莫烦TensorFlow_11 MNIST优化使用CNN

    import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data #number 1 to 10 d ...

  8. python27期day16:序列化、json、pickle、hashlib、collections、软件开发规范、作业。

    序列化模块:什么是序列化呢? 序列化的本质就是将一种数据结构(如字典.列表)等转换成一个特殊的序列(字符串或者bytes)的过程就叫做序列化.将这个字典直接写入文件是不可以的,必须转化成字符串的形式, ...

  9. Win10 Mactype 字体优化

    1.下载安装 Mactype :http://www.mactype.net/ 2. 打开MacType Tray.exe,右键其在任务栏图标就能选择配置文件. 分享一个配置文件: [General] ...

  10. 一些开源cdc框架以及工具

    以下是一些cdc工具,没有包含商业软件的 zendesk maxwell 参考地址 https://github.com/zendesk/maxwell 功能 mysql 2 json 的kafaa ...