題名 | 著者 | 日付 | CREATETIME | カテゴリ |
---|---|---|---|---|
WPFイベントトリガ複数StylusPlugIn順
|
lindexi
|
2019年11月29日午前10時20分41秒0800
|
2019年10月19日午前9時18分35秒0800
|
WPF
|
あなたがWPFでStylusPlugInを使用する場合は複数の要素と同じインタフェースで同じ時間は、その後のイベントのStylusPlugIn配列に結合している私のアドバイスは、あなたがたlet StylusPlugInを重ねStylusPlugInの書き込みを理解していない前に、StylusPlugInオーバーラップを聞かせすることはなく、意志の混乱を引き起こしましたコード。移動するには、小さなパートナーがあるかもしれないので要素を聞かせて、あなたの行動規範ではなく書き込み前と同じ
複数StylusPlugIn追加要素が重なっていない場合は、すべての要素のは、期待通りに動作します。すなわち、要素は、対応する要素法StylusPlugInがトリガされる点です
この記事は、より複雑であるため、主要な原則は非常にこれだけ下の画像を見ることができる小さなパートナーの現象を理解したい、退屈されます
赤いボックスの異なる要素を表す私は2つの異なるボックスを使用する青色追加の要素を表して、通常のコンテナを表しStylusPlugIn
2つの重複する要素を有する容器のStylusPlugInイベント、2つの要素が要素の下にタッチで異なるスレッドをトリガすること、同時にトリガされ、素子の最上層に主トリガスレッドであろう
コンテナの複数の重複要素を使用すると、要素の上部と下部には要素の下部にタッチで別のスレッドをトリガーするイベントをトリガし、要素の最上層にメイントリガースレッドになることを知っているだろう
それは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 触发,而在主线程命中测试也是返回第二个控件
所以第一个控件没有被触发事件