原文: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. MySQL的过滤(极客时间学习笔记)

    数据过滤 SQL的数据过滤, 可以减少不必要的数据行, 从而可以达到提升查询效率的效果. 比较运算符 在SQL中, 使用WHERE子句对条件进行筛选, 筛选的时候比较运算符是很重要. 上面的比较运算符 ...

  2. adb 连接夜神和逍遥模拟器

    然后运行cmd命令,cd到夜神安装目录,执行命令 adb connect 127.0.0.1:62001 nox_adb.exe connect 127.0.0.1:62001 逍遥模拟器 adb c ...

  3. Linux编译安装相关命令

    1.  configuer configure  是一个shell脚本,用来检测安装平台是否符合要求,并通过配置参数生成makefile文件 2. make  (make all 的简写) 编译命令, ...

  4. BitTorrent协议与MagNet协议原理【转】

    转自:https://blog.csdn.net/u012785382/article/details/70674875 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog ...

  5. centos7 yum 安装lnmp

    centos7 yum 安装lnmp   安装7.2把7.1改成7.2就行 使用第三方扩展epel源安装php7.2 #移除旧版php [root@web02 ~]# yum remove php-m ...

  6. zz模型剪枝

    论文总结 - 模型剪枝 Model Pruning  发表于 2018-10-03 模型剪枝是常用的模型压缩方法之一.这篇是最近看的模型剪枝相关论文的总结. Deep Compression, Han ...

  7. nwjs-简单应用于基本结构

    简单应用 1.在解压目录下创建文件夹dmeo01 2.在文件夹中创建index.html和package.json两个文件 3.index.html内容: <!DOCTYPE html> ...

  8. cerely-分布式异步任务队列

    Celery 是一个强大的 分布式任务队列 的 异步处理框架,它可以让任务的执行完全脱离主程序,甚至可以被分配到其他主机上运行.我们通常使用它来实现异步任务(async task)和定时任务(cron ...

  9. Django-Debug-Toolbar插件

    目录 django配置插件: 介绍: 安装及配置: 优化ORM: django配置插件: ---配置Django-Debug-Toolbar 介绍: Django-Debug-Toolbar是项目开发 ...

  10. LoadRunner名词解释

    Transactions(用户事务分析):用户事务分析是站在用户角度进行的基础性能分析. 1.Transation Sunmmary(事务综述) 对事务进行综合分析是性能分析的第一步,通过分析测试时间 ...