2019-10-21-WPF-多个-StylusPlugIn-的事件触发顺序
| title | author | date | CreateTime | categories |
|---|---|---|---|---|
|
WPF 多个 StylusPlugIn 的事件触发顺序
|
lindexi
|
2019-10-21 08:33:15 +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 1 和 Stylus 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 1 和 Stylus 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-10-21-WPF-多个-StylusPlugIn-的事件触发顺序的更多相关文章
- 【RL-TCPnet网络教程】第21章 RL-TCPnet之高效的事件触发框架
第21章 RL-TCPnet之高效的事件触发框架 本章节为大家讲解高效的事件触发框架实现方法,BSD Socket编程和后面章节要讲解到的FTP.TFTP和HTTP等都非常适合使用这种方式 ...
- 2019.10.21 csp-s模拟测试81 反思总结
T1: 把每一行状压,按行DP.设fi,j,k,i表示第几行,j是当前行的1覆盖状态,k是当前行选择按钮的状态.转移的时候枚举j和k,再枚举下一层的按钮选择情况l.如果l和j可以全覆盖当前层则转移合法 ...
- JAVA课堂作业(2019.10.21)
1. 代码: package class20191021; class Grandparent { public Grandparent() { System.out.println("Gr ...
- 背水一战 Windows 10 (21) - 绑定: x:Bind 绑定, x:Bind 绑定之 x:Phase, 使用绑定过程中的一些技巧
[源码下载] 背水一战 Windows 10 (21) - 绑定: x:Bind 绑定, x:Bind 绑定之 x:Phase, 使用绑定过程中的一些技巧 作者:webabcd 介绍背水一战 Wind ...
- 2019.3.18考试&2019.3.19考试&2019.3.21考试
2019.3.18 C O D E T1 树上直接贪心,环上for一遍贪心 哇说的简单,码了将近一下午终于码出来了 感觉自己码力/写题策略太糟糕了,先是搞了一个细节太多的写法最后不得不弃疗了,然后第二 ...
- Daily Scrum 10.21
然后由于服务器端有变化,另外具体IDE已经确定,接下来对已经分配下去的任务做些细节补充: 10.20日晚所有人必须完成AS的配置,统一版本为1.3.2,安卓版本为4.4.0,可视化界面手机为Nexus ...
- 第9次Scrum会议(10/21)【欢迎来怼】
一.小组信息 队名:欢迎来怼小组成员队长:田继平成员:李圆圆,葛美义,王伟东,姜珊,邵朔,冉华小组照片 二.开会信息 时间:2017/10/21 17:20~17:45,总计25min.地点:东北师范 ...
- Clover KextsToPatch 使用方法 2015.10.21
Clover KextsToPatch 使用方法 2015.10.21 前些天,因为 Thinkpad X230 BIOS 白名单限制,给她换了一块 ar9285 无线网卡,只是因为这块网卡正好可 ...
- WPF 高速书写 StylusPlugIn 原理
原文:WPF 高速书写 StylusPlugIn 原理 本文告诉大家 WPF 的 StylusPlugIn 为什么能做高性能书写,在我的上一篇博客和大家介绍了 WPF 的触摸原理,但是没有详细告诉大家 ...
- 2019.10.29 CSP%您赛第四场t2
我太菜了我竟然不会分层图最短路 ____________________________________________________________________________________ ...
随机推荐
- ElasticSearch基本操作(安装,索引的创建和删除,映射)
ElasticSearch基于Lucene的搜索服务器,支持分布式,提供REST接口,可用于云计算,可以实现实时搜索,开源免费.这时很官方的一句话,在使用之前,我们简单的介绍一下安装过程.在官网下载之 ...
- TZOJ 4292 Count the Trees(树hash)
描述 A binary tree is a tree data structure in which each node has at most two child nodes, usually di ...
- centos7.6 安装jdk1.8
1. 下载 jdk-8u211-linux-x64.tar.gz文件. 2. 创建/opt/soft目录,# cd /opt, # mkdir soft, #tar -zxvf jdk-8u211- ...
- mongodb集群搭建过程记录
mongodb集群搭建花费比较长的时间,在此记录下过程,方便以后使用 一 软件环境 系统:ubuntu 18.04,mongodb 社区版4.2 https://docs.mongodb.com/ma ...
- 玩转webpack之webpack的基本知识
相信看了gulp教程的小伙伴肯定都可以很容易的掌握gulp了.它已经没有什么可以值得去思考的东西了,如果你已经看懂它就是单纯的在布置任务,然后利用插件的功能去执行任务.最后发布任务,pipe的理念来和 ...
- Java是如何实现跨平台的
一.Java是如何实现跨平台的 1.我们编写的Java源码,编译后会生成一种 .class 文件,称为字节码文件 2.Java虚拟机JVM就是负责将字节码文件翻译成特定平台下的机器码然后运行.也就是说 ...
- django中的聚合索引
Django(元信息)元类建索引 ORM查询(sql优化)优化 自定义聚合函数 Django的元类建索引————索引:索引的一个主要目的就是加快检索表中数据,索引是经过某种算法优化过的,因而查找次数要 ...
- linux如何将分组权限置为空
两种方法 方法一:使用-符号 chmod g=- monkey.py#可以单独指定一个 方法二:简写方式,用0表示 chmod 740 monkey.py#必须同时指定三个的权限
- win10 下安装docker,创建镜像,push镜像到私有仓库,创建私有仓库,修改镜像仓库地址
通过连接下载window docker安装文件,https://download.docker.com/win/stable/Docker%20for%20Windows%20Installer.ex ...
- new 在C++ 中的用法
我对C++一无所知 看参考手册 来看一下参考手册,总共有三种用法 下面是网站上给出的例子 // operator new example #include <iostream> // st ...