可以/应该MouseEnter事件泡沫?

时间:2011-02-22 10:11:00

标签: wpf wpf-controls routed-events

在任何情况下都可以让MouseEnter事件冒泡吗?

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

在:父母:s7b,时间戳:37989609

在:父:s2,时间戳:37989609

In:parent:Root,timestamp:37989609

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

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

当我正在为这个问题制作一个简约的复制品时,有人会建议可能导致这种行为吗?


这是复制品:

  1. 创建两个自定义控件,一个是常规控件,另一个是事件接收器。
  2. 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. 最后我的测试工具的标记:
    2. 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>
      

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

      干杯。

3 个答案:

答案 0 :(得分:2)

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

  

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

MSDN表示IsMouseOverIsMouseOver从false更改为true时触发。查看IsMouseOver IsMouseOver以下引用:

  

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

正如我们都同意的那样,空背景不支持交互。关于VisualTreeHelperMSDN article有很多警告,但从实际应用中可以明显看出,这个值不会为空背景切换。但是,该定义确实说明如果鼠标“位于”元素范围内的任何视觉子元素之上,那么c1将改变禁止几个奇怪的警告。但是,空背景不是这些警告之一。

使用MSDN articlec2快速查看控件的可视树,显示所有三个灰色网格都是c3的视觉子项,最右边的两个网格是可视化子项IsMouseOver的最右边网格是c1的视觉子节点。这是预期的,因为所有内容控件都嵌套在一起。

通过监控 private void MouseMove_Callback(Object sender, MouseEventArgs e) { if (c1.IsMouseOver) MessageBox.Show("Mouse is Over c1!"); } IsMouseOver属性,您可以轻松看到当鼠标触及灰色方块时,属性值会更改为true。您可以通过向主窗口的鼠标移动事件添加回调来验证这一点。我使用了以下回调:

c1

您会注意到,无论您位于IsMouseOver c1的三个灰色方块中的哪一个都设置为true。这表示MouseEnterc1超过三个方格中的任意一个时变为true,因此MSDN声明的声明为真。无论您触摸哪个灰色方块,c1都会MouseEnter被解雇,因为所有三个灰色方块都位于{{1}}视觉树中,并且未通过警告从鼠标命中测试中消除(例如null背景警告。)

正如MSDN声称的那样,{{1}}事件作为应用程序中的直接事件进行响应。

答案 1 :(得分:1)

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

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

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

- 编辑

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

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

答案 2 :(得分:0)

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

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

由于附加事件的优点,MTC成为Source&amp;鼠标事件的原始源,也将它们的IsMouseOver设置为true,这与系统的其他部分无法相处。

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

非常感谢任何建议。