原文: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. Qt发送邮件

    首先下载支持库 https://download.csdn.net/download/zhangxuechao_/10598108 #ifndef MAIL_H #define MAIL_H #inc ...

  2. 团队作业第3周——需求改进&系统设计

    目录 团队作业第3周--需求改进&系统设计 1.需求&原型改进 2.系统设计 3.Alpha任务分配计划 4.测试计划 1 测试术语 4.2 有关项目人员组成 2 任务概述 3.测试策 ...

  3. JS 判断设备来源

    1.js代码判断当前设备: function deviceType(){ var ua = navigator.userAgent; var agent = ["Android", ...

  4. Android APP之WebView如何校验SSL证书

    Android系统的碎片化很严重,并且手机日期不正确.手机根证书异常.com.google.android.webview BUG等各种原因,都会导致WebViewClient无法访问HTTPS站点. ...

  5. SQLserver 《深入分析sqlserver 2008》

    PDF版本: 链接:https://pan.baidu.com/s/1bheII-EdyleVJaR5r9lT9Q 提取码:f8zz

  6. Java八大排序之希尔(Shell)排序

    希尔排序(Shell's Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本.希尔排序是非稳定排序算法.该 ...

  7. vue-router有哪几种导航钩子 keep-alive的详细用法 解决跨域

                1===>vue-router有哪几种导航钩子?        第一种:是全局导航钩子:router.beforeEach(to,from,next)        第二 ...

  8. 2019 China Collegiate Programming Contest Qinhuangdao Onsite

    传送门 D - Decimal 题意: 询问\(\frac{1}{n}\)是否为有限小数. 思路: 拆质因子,看是不是只包含2和5即可,否则除不尽. Code #include <bits/st ...

  9. LOJ 546: 「LibreOJ β Round #7」网格图

    题目传送门:LOJ #546. 题意简述: 题目说的很清楚了. 题解: 将不包含起点或障碍物的连续的行或列缩成一行或一列,不会影响答案. 处理过后,新的网格图的行数和列数最多为 \(2k + 3\). ...

  10. 跟着ALEX 学python day5 模块

    文档内容学习于 http://www.cnblogs.com/xiaozhiqi/  模块 1.模块:用来从逻辑上组织python代码(变量,函数,类,逻辑:实现一个功能),本质就是.py结尾的pyt ...