鼠标进入事件能否冒泡?

3

在任何情况下都有可能得到MouseEnter事件冒泡吗?

MSDN表示它是具有直接路由策略的附加事件,这从技术上排除了这种可能性。我有一个相当复杂的控件(实质上是由网格、堆栈面板和内容控件组成的层次结构)。我似乎可以从下往上传播MouseEnter事件,以下是从OnMouseEnter处理程序中获取的调试转储(我在层次结构的不同级别包含了相同的自定义控件,它处理MouseEnter,因此我有一个集中监听该事件的地方):

进入:parent:s7b,时间戳:37989609

进入:parent:s2,时间戳:37989609

进入:parent:Root,时间戳:37989609

s7b、s2和Root是FrameworkElement名称,时间戳是来自MosueEnter事件的e.Timestamp。

假设路由策略是Direct,WPF如何决定事件发起者?它是否遍历可视树,直到找到第一个具有附加MouseEnter事件的FrameworkElement?

虽然我正在为问题工作创建一个最小化的重现集,但有人能否建议什么可能导致这种行为?


这里是重现:

  1. 创建两个自定义控件,一个是常量控件,另一个是事件接收器。

1.1. MyContentControl

代码:

    public class MyContentControl : ContentControl
    {
        static MyContentControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(MyContentControl), 
                new FrameworkPropertyMetadata(typeof(MyContentControl)));
        }

        protected override void OnMouseEnter(MouseEventArgs e)
        {
            if (e.Source == e.OriginalSource
                && e.Source is MyContentControl)
            {
                Debug.Write(string.Format("mouseenter:{0}, timestamp:{1}\n",
                    (e.Source as MyContentControl).Name,
                    e.Timestamp));
            }

            base.OnMouseEnter(e);
        }
    }

XAML:

<Style TargetType="{x:Type local:MyContentControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:MyContentControl}">
                    <StackPanel Orientation="Horizontal">
                        <local:MouseEventReceiver />
                        <ContentPresenter />
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

1.2 MouseEventReceiver

代码:

public class MouseEventReceiver : Control
{
    static MouseEventReceiver()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(MouseEventReceiver), 
            new FrameworkPropertyMetadata(typeof(MouseEventReceiver)));
    }
}

XAML:

<Style TargetType="{x:Type local:MouseEventReceiver}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <Grid Background="LightGray" Width="20" Height="20" Margin="5"></Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
  1. 最后是我的测试工具的标记:

XAML:

<Window x:Class="MouseTricks.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MouseTricks"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <local:MyContentControl x:Name="c1">
            <local:MyContentControl x:Name="c2">
                <local:MyContentControl x:Name="c3" />
            </local:MyContentControl>
        </local:MyContentControl>
    </Grid>
</Window>

为了重现问题,只需将光标悬停在最右边的灰色正方形上,观察“Debug Output”窗口,您会看到三个条目,而我只期望看到一个。

祝好。

3个回答

2
也许更详细的描述会有所帮助。在MSDN文章中,关于Mouse.MouseEnter事件,引用了以下语句:

虽然此事件用于跟踪鼠标何时进入元素,但它还报告了IsMouseOver属性已从false更改为true。

MSDN表示,当IsMouseOver从false更改为true时,Mouse.MouseEnter会触发。查看IsMouseOverMSDN文章,引用了以下语句:

获取一个值,该值指示鼠标指针是否位于此元素上方(包括其边界内的可视子元素)

正如我们所同意的那样,空背景不支持交互。关于null background issueIsMouseOver,有很多注意事项,但从实际应用来看,对于一个空背景,这个值并没有被改变。然而,定义中确实说,如果鼠标在元素范围内的任何可视子级上方,则IsMouseOver将发生变化,尽管存在一些奇怪的注意事项。然而,空背景不是这些注意事项之一。
使用snoop utilityVisualTreeHelper快速查看您控件的可视树,可以看到所有三个灰色网格都是c1的可视子级,最右边的两个网格是c2的可视子级,而最右边的网格是c3的可视子级。这是预期的,因为您的所有内容控件都嵌套在彼此之中。
通过监视IsMouseOver属性,您可以轻松地看到当鼠标触摸灰色正方形时,属性值会更改为true。 您可以通过将回调添加到主窗口的鼠标移动事件来验证此操作。 我使用了以下回调函数:
    private void MouseMove_Callback(Object sender, MouseEventArgs e)
    {
        if (c1.IsMouseOver)
            MessageBox.Show("Mouse is Over c1!");
    }

您会注意到,无论您在哪个灰色正方形上,IsMouseOver对于c1都设置为true。这表明当它位于任何三个正方形之一上方时,IsMouseOver将对c1设置为true,因此MSDN所述的内容是正确的。无论您触摸哪个灰色正方形,MouseEnter都应该并且确实会为c1触发,因为所有三个灰色正方形都在c1的可视树中,并且没有通过某些限制(如空背景限制)从鼠标命中测试中消除。

MouseEnter事件在您的应用程序中作为直接事件响应,就像MSDN所述一样。


你怎么这么固执?对于鼠标透明控件,IsMouseOver 总是为 false,永远不会被设置为 true。 - user572559
创建一个空白模板的控件,创建OnPropertyChanged重写方法,检查e.Property == IsMouseOverProperty - 它永远不会被设置为true,没有机会。 - user572559
当然不会,因为MSDN的第二个引用说,需要在被点击的元素的可视树中有某些东西(除了一些警告)。其中之一是对具有空背景的内容控件的限制。空背景不提供任何鼠标交互,您可以轻松验证。在您的示例代码中,您可以使用VisualTreeHelper.GetChildrenCount()轻松验证您的空白控件没有可视子项。在这种情况下,绝对没有可能被点击,因此没有鼠标交互。 - Black Jack Pershing
请不要感到沮丧。我正在尽力帮助你。如果您认为我在上面的帖子中提供的证据有问题,请说明您不同意的具体证据以及原因。如果您要说某些事情是错误的,您需要清楚地指出您认为哪里有问题,并最好驳斥给出的证据。 - Black Jack Pershing
稍作修改。警告不是ContentControl具有空背景,而是它没有任何内容。测试带有和不带有内容的ContentControl上的IsMouseOver可以证明这一点。我不应该说警告是空背景,因为当没有任何内容时,背景没有任何影响。显然,没有任何内容的ContentControl也是一个警告。MSDN表示,HitTestCore被重写用于ContentControl(请参见ContentControl.HitTestCore的MSDN条目),因此WPF的开发人员必须出于某种原因将其作为另一个警告。 - Black Jack Pershing
你很棒,抱歉我生气了 :) WPF 本身就是一个大的警告,但不知怎么地,我爱上了它。干杯。 - user572559

1

由于这是一个复杂的控件,当您用鼠标进入根元素时,很可能同时也进入了s7b和s2。由于这三个元素都已注册MouseEnter事件,如果鼠标可以同时进入所有三个元素,则它们应该同时响应。

这可能看起来像事件正在沿着可视树冒泡,因为您恰好在类似大小的可视父级行中注册了MouseEnter。如果我在StackPanel中定义一个Button,并将按钮拉伸以填充StackPanel,并为两者都注册MouseEnter事件,那么每当鼠标进入Button时,它默认情况下也会进入父级(StackPanel)。在这种情况下,当事实上只是发生了两个单独元素的直接事件时,它可能看起来像事件正在沿着可视树冒泡。

如果您正在创建一个复杂的控件,通常您会希望为整个控件或特定部分的控件设置一个MouseEnter回调。您确定需要针对整个控件以及控件的各个部分进行回调吗?

-编辑

刚看到你的新帖子。我尝试了你的代码并注意到MyContentControl实例的内容都是嵌套的。由于MyContentControl类派生自内容控件,因此控件会被拉伸以适应可用空间。您可以通过将边框属性添加到MyContentControl类来查看此情况。由于MyContentControl的背景默认为null,因此只有在触摸其中一个灰色框时才会触发MouseEnter。
第一个MyContentControl创建一个水平Stackpanel,并添加灰色框和内容呈现器。位于第一个灰色框所在网格右侧的任何内容都将自动位于c2和/或c3中,因为c1的内容呈现器将被拉伸以适应具有固定高度和宽度的窗口大小。这就是为什么当你悬停在c2上时,你会得到c1和c2的MouseEnter,因为当触摸灰色框时,鼠标已进入c1的内容呈现器,同时鼠标也进入了c2的灰色框。类似的逻辑也适用于理解c3的情况。

MyContentControl 必须能够接收鼠标事件,因为它是你的代码中仅注册了鼠标事件的类。MyContentControl 的背景为 null,然而灰色框是 MyContentControl 的一个可视化子元素,其背景不为 null,因此当鼠标与灰色方块交互时,鼠标事件将被触发。 - Black Jack Pershing
嗨,我需要没有边框来理解我的控件如何工作:)我们正在震动空气。并非所有事情都有简单的解释,其中一些根本没有解释(希望不是这种情况)。无论如何,感谢您的建议。为了证明内容呈现器与问题无关,请小心驾驶鼠标穿过第三个和第二个框之间的道路,不会触发任何事件。 - user572559
自然地,除非您触摸灰色框,否则不会引发任何事件,因为背景为空。如果您触摸中间的灰色框,则直接触摸c2的灰色框,因此会出现MouseEnter事件,并且您间接地触摸到了c1的内容呈现器,因为c1的内容呈现器正在呈现c2的内容。因此,触摸c2的灰色框等同于触摸属于c1的内容,因为c2的灰色框是c1内容的一部分。当您触摸中间的框时,c1和c2都会触发MouseEnter。 - Black Jack Pershing
间接触摸不应计算在内,我们在处理一个直接路由事件。 - user572559
ContentPresenter并不仅仅是一个背景。如果ContentPresenter只包含一个背景,并且该背景被设置为null,那么您的说法是正确的,不应该有任何鼠标交互。然而,ContentPresenter还有其他没有空背景(灰色正方形)的内容。空背景只会消除与背景的鼠标交互,而不是内容。由于内容是包含它的控件的可视树的一部分,这意味着当您触摸正方形时,您正在与包含控件以及内容进行交互。 - Black Jack Pershing
显示剩余3条评论

0

具有鼠标不透明子元素(MOC)的鼠标透明控件(MTC)无法正确处理鼠标事件。(我倾向于称它们为布局控件。)

我可能错了,但在我看来,这似乎是一个错误。我猜罪魁祸首是MTC无法处理鼠标输入,但却表现得不一致。

由于附加事件的优点,MTC成为鼠标事件的源和原始源,它们的IsMouseOver也被设置为true,这与系统的其他部分不兼容。

解决方法是-仅订阅您控件中鼠标不透明的部分以进行鼠标事件。乍一看听起来很可怕,但只要使用命令,您就不应该失去太多灵活性。

非常感谢任何建议。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接