3

在任何情况下都有可能让 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>

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

干杯。

4

3 回答 3

2

也许更详细的描述会有所帮助。在MSDN 文章Mouse.MouseEnter,引用了以下内容:

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

MSDN 说当从 false 变为 trueMouse.MouseEnter时触发。IsMouseOver查看MSDN 文章IsMouseOver获取以下引用:

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

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

使用snoop 实用程序快速查看控件的可视化树,或者VisualTreeHelper显示所有三个灰色网格都是 的可视子项c1,最右边的两个网格是 的可视子项c2,最右侧的网格是 的可视子项c3。这是意料之中的,因为您的所有内容控件都相互嵌套。

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

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

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

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

于 2011-02-23T17:26:21.940 回答
1

由于这是一个复杂的控件,因此当您使用鼠标输入 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 的情况。

于 2011-02-22T15:06:02.797 回答
0

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

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

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

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

任何建议都受到高度赞赏。

于 2011-02-22T16:23:22.940 回答