【问题标题】:Can/Should MouseEnter event bubble?可以/应该 MouseEnter 事件冒泡吗?
【发布时间】:2011-02-22 10:11:00
【问题描述】:

是否有可能在任何情况下让 MouseEnter 事件冒泡?

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

在:父:s7b,时间戳:37989609

在:父:s2,时间戳:37989609

在:父:根,时间戳:37989609

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

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

虽然我正在为该问题制作一个简约的重现集,但有人可以提出可能导致该行为的原因吗?


这是再现:

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

1.1。我的内容控件

代码:

    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 鼠标事件接收器

代码:

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>

为了重现问题,只需将鼠标悬停在最右边的灰色方块上并观察“调试输出”窗口,您会在其中看到三个条目,而我预计只有一个。

干杯。

【问题讨论】:

    标签: wpf wpf-controls routed-events


    【解决方案1】:

    也许更详细的描述会有所帮助。在Mouse.MouseEnter 上的MSDN article 中引用了以下引用:

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

    MSDN 说 Mouse.MouseEnterIsMouseOver 从 false 变为 true 时触发。查看MSDN articleIsMouseOver 引用如下:

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

    我们都同意,空背景不支持交互。关于IsMouseOvernull background issue 有很多警告,但从实际应用中可以明显看出,该值不会针对空背景进行切换。但是,定义确实表明,如果鼠标“位于”元素范围内的任何可视子项上,则IsMouseOver 将发生变化,除非有几个奇怪的警告。但是,空背景不是这些警告之一。

    使用snoop utilityVisualTreeHelper 快速查看控件的可视化树会发现所有三个灰色网格都是c1 的可视子级,最右边的两个网格是c2 的可视子级,并且最右边的网格是c3 的视觉子级。这是意料之中的,因为您的所有内容控件都相互嵌套。

    通过监视IsMouseOverc1 属性,您可以很容易地看到,当鼠标触摸灰色方块时,属性值变为true。您可以通过向主窗口的鼠标移动事件添加回调来验证这一点。我使用了以下回调:

        private void MouseMove_Callback(Object sender, MouseEventArgs e)
        {
            if (c1.IsMouseOver)
                MessageBox.Show("Mouse is Over c1!");
        }
    

    您会注意到,无论您在IsMouseOver 上的三个灰色方块中的哪个c1 都设置为true。这表明当 c1 在三个正方形中的任何一个上时,IsMouseOver 更改为 true,因此 MSDN 所做的声明是正确的。 MouseEnter 应该并且确实会因为 c1 而被解雇,无论您触摸哪个灰色方块,因为所有三个灰色方块都在 c1s 视觉树中,并且不会因警告(例如空背景警告)而从鼠标命中测试中消除)。

    正如 MSDN 所声称的,MouseEnter 事件在您的应用程序中作为直接事件进行响应。

    【讨论】:

    • 你怎么这么固执?对于鼠标透明控件,IsMouseOver 始终为 false,永远不会设置为 true。
    • 使用空白模板创建控件,创建 OnPropertyChanged 覆盖,检查 e.Property == IsMouseOverProperty - 它永远不会设置为 true,没有机会。
    • 当然不会,因为 MSDN 的第二条引述说,@987654346 的元素的可视化树中需要有一些东西(除了一些警告) @ 改变。这些警告之一是针对具有空背景的内容控件。空背景不提供鼠标交互,您可以轻松验证。在您的示例代码中,您可以使用VisualTreeHelper.GetChildrenCount() 轻松验证您的空白控件没有可视子项。在这种情况下,绝对没有任何东西可以被击中,因此没有鼠标交互。
    • 请不要沮丧。我正在努力帮助你。如果您认为我在上述帖子中提供的证据有问题,请说明您不同意的具体证据以及原因。如果您要说某事是错误的,则需要清楚地指出您认为错误的地方,并最好驳斥所提供的证据。
    • 轻微修改。需要注意的不是ContentControl 有一个空背景,而是它没有任何内容。在有和没有内容的ContentControl 上测试IsMouseOver 表明了这一点。我不应该说警告是空背景,因为当没有任何内容时,背景没有任何区别。显然,没有任何内容的ContentControl 也是一个警告。 MSDN 说,HitTestCoreContentControl 覆盖(请参阅 ContentControl.HitTestCore 上的 MSDN 条目),因此 WPF 的开发人员一定出于某种原因对此提出了另一个警告。
    【解决方案2】:

    由于这是一个复杂的控件,当您使用鼠标输入 Root 元素时,您似乎也同时输入了 s7b 和 s2。由于所有三个元素都注册了 MouseEnter 事件,因此如果鼠标可以同时进入所有三个元素,它们都应该在完全相同的时间做出响应。

    看起来该事件在视觉树上冒泡,因为您碰巧为一行相似大小的视觉父项注册了 MouseEnter。如果我在 StackPanel 中定义一个 Button,并且该按钮拉伸以填充 StackPanel 并为 MouseEnter 事件注册两者,那么每当鼠标进入 Button 时,默认情况下它也会进入父级(StackPanel)。在这种情况下,事件看起来像是在视觉树中冒泡,而实际上它只是同时发生的两个独立元素的直接事件。

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

    -编辑

    刚刚看到你的新帖子。我试过你的代码,我注意到内容 MyContentControl 实例都是嵌套的。由于 MyContentControl 类派生自内容控制,因此控件被拉伸以适应可用空间。您可以通过向 MyContentControl 类添加边框属性来查看这一点。由于默认情况下 MyContentControl 的背景为 null MouseEnter 仅在触摸其中一个灰色框时才会触发。

    第一个 MyContentControl 创建一个水平 Stackpanel 并添加灰色框,然后添加一个内容呈现器。网格右侧带有第一个灰色框的任何内容都将自动位于 c2 和/或 c3 中,因为来自 c1 的内容呈现器将被拉伸以适应具有固定高度和宽度的窗口的大小。这就是为什么当您将鼠标悬停在 c2 上时,您会得到 c1 和 c2 的 MouseEnter,因为当触摸灰色框时,鼠标已进入 c1 的内容呈现器,并且鼠标也已进入 c2 的灰色框。类似的逻辑可以用来理解c3的情况。

    【讨论】:

    • 嗨,我无法想象鼠标是如何同时进入元素的,这是不可能的,无论元素是否具有重叠区域。它始终是一个元素。有趣的是,该事件在附加了 MouseEnter 事件的第一个元素上被触发。 Source 和 OriginalSource 是 MyContentControl,所以它根本不报告 MouseEventReceiver,即使它是为它提出的。 MyContentControl 没有背景,因此无法接收鼠标事件。 - 我想我可能知道发生了什么。
    • MyContentControl 必须能够接收鼠标事件,因为它是代码中唯一注册到鼠标事件的类。 MyContentControl 的背景为空,但是,灰色框是 MyContentControl 的可视子对象,没有空背景,因此当鼠标与灰色方块交互时,将引发鼠标事件。
    • 嗨,我不需要国界来理解我的控制是如何工作的:)我们正在震动空气。不是每件事都有一个简单的解释,其中一些根本没有解释(希望不是这样)。无论如何感谢您的建议。为了证明 Content Presenters 与问题无关 - 在第三个和第二个框之间的道路上小心驾驶鼠标,不会引发任何事件。
    • 自然不会引发任何事件,除非您触摸灰色框,因为背景为空。如果您触摸中间的灰色框,那么您将直接触摸 c2 的灰色框,因此会有一个 MouseEnter 事件,并且您正在间接触摸 c1 的内容展示器,因为 c1 的内容展示器正在展示 c2 的内容。所以触摸 c2 的灰色框就相当于触摸了属于 c1 的内容,因为 c2 的灰色框是 c1 内容的一部分。内容(c2 的灰色框)恰好在 c1 的内容展示器中,因此当您触摸中间框时,MouseEnter 会为 c1 和 c2 触发。
    • 间接触摸不应该计算在内,我们在这里处理的是直接路由事件。
    【解决方案3】:

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

    我可能是错的,但对我来说这似乎是一个错误。我可以猜测,罪魁祸首是 MTC 无法处理鼠标输入但假装这样做相当不一致。

    由于附加事件的优点,MTC 成为鼠标事件的 Source 和 OriginalSource,并且它们的 IsMouseOver 被设置为 true,这与系统的其他部分不能很好地配合。

    解决方法是 - 仅将控件的鼠标不透明部分订阅到鼠标事件。乍一看听起来很可怕,但如果你使用命令,你不应该失去太多的灵活性。

    非常感谢任何建议。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-02-05
      • 2011-10-20
      • 2011-08-23
      • 1970-01-01
      • 2021-08-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多