2019-11-29-WPF- more -StylusPlugIn- event triggers order

Original: 2019-11-29 WPF - multiple-event trigger sequence -StylusPlugIn-

title author date CreateTime categories
WPF event triggers multiple StylusPlugIn order
lindexi
2019-11-29 10:20:41 +0800
2019-10-19 09:18:35 +0800
WPF

If you use StylusPlugIn in WPF the same time in the same interface with multiple elements are then combined with StylusPlugIn sequence of events triggered will mess my advice is not to let StylusPlugIn overlap, before you do not understand StylusPlugIn write overlapping let StylusPlugIn code. Because there may be a small partner to move an element let your code of conduct and not the same as before writing

If multiple StylusPlugIn additional elements do not overlap, then all of the elements will work in line with expectations. That is the point to which element will trigger the corresponding element method StylusPlugIn

Because this article is more complex, the main principle is very boring, so just want to understand the phenomenon of small partners can look at the picture below

I will use two different boxes represent different elements of the red box represents an ordinary container, while the blue represents additional elements StylusPlugIn

StylusPlugIn event of the container with two overlapping elements, two elements will be triggered simultaneously, that will trigger a different thread in touch in the bottom of the elements, and in the uppermost layer of the element will be the main trigger thread

With multiple overlapping elements of the container, you will know that the top and bottom of the element will trigger events that will trigger a different thread in touch at the bottom of the elements, and in the uppermost layer of the element will be the main trigger thread

If it is a StylusPlugIn additional container, contains a StylusPlugIn additional element, the element will only trigger trigger event thread in touch

代码放在 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 触发,而在主线程命中测试也是返回第二个控件

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

Guess you like

Origin www.cnblogs.com/lonelyxmas/p/12075852.html