从一个面板及其子控件中触发的MouseEnter和MouseLeave事件

16

我有一个包含子控件的Panel

如果我处理PanelMouseEnterMouseLeave事件以及它的子控件的MouseEnterMouseLeave事件,这些事件会按照以下顺序触发:

Panel.MouseEnter
Panel.MouseLeave
Child1.MouseEnter
Child1.MouseLeave
Panel.MouseEnter
Panel.MouseLeave

但我需要以下顺序:

Panel.MouseEnter
Child1.MouseEnter
Child1.MouseLeave
Panel.MouseLeave

这是否可能?

11个回答

22

如果您不介意创建一个用户控件(派生自面板或其他您希望的父容器), 请重写父容器的OnMouseLeave方法,使其如下所示..

protected override void OnMouseLeave(EventArgs e)
{

    if(this.ClientRectangle.Contains(this.PointToClient(Control.MousePosition)))
         return;
    else
    {
        base.OnMouseLeave(e);
    }
}

那么,事件触发将按照所需的顺序进行。


谢谢和+1,这给了我一个想法:检测鼠标位置是否在子菜单下拉框中。 - sihirbazzz
1
好主意。但是如果包含的控件在边框上,它将无法工作。(请给下面的用户[https://dev59.com/n3E85IYBdhLWcg3w3Xdp#19361582]以功劳) - ispiro
1
不需要创建用户控件。只需在父控件和子控件的MouseLeave事件中使用此代码即可完美运行。 - Doug

5
老鼠进入子控件时,“离开”面板,因此会触发事件。您可以在面板的MouseLeave事件处理程序中添加以下内容:
// Check if really leaving the panel
if (Cursor.Position.X < Location.X ||
    Cursor.Position.Y < Location.Y ||
    Cursor.Position.X > Location.X + Width - 1 ||
    Cursor.Position.Y > Location.Y + Height - 1)
{
    // Do the panel mouse leave code
}

1
谢谢,我试过了,但是它有一个bug:由于Cursor.Position是当前位置而不是鼠标离开控件时的确切位置,所以存在竞争条件。 - DxCK

4

Jimmy T. 是正确的。如果父控件(面板)边缘和子控件之间没有足够的空间,就会出现问题。

下面是我在 UserControl 派生类中解决此问题的方式:

    public CSStackPanelItem()
    {
        InitializeComponent();

        MouseEnter += new EventHandler(CSStackPanelItem_MouseEnter);

        foreach (Control child in Controls)
        {
            child.MouseEnter += (s, e) => CSStackPanelItem_MouseEnter(s, e);
            child.MouseLeave += (s, e) => OnMouseLeave(e);
        }
    }

    protected override void OnMouseLeave(EventArgs e)
    {
        if (this.ClientRectangle.Contains(this.PointToClient(Control.MousePosition)))
            return; //suppress mouse leave event handling

        if (m_bIsHover)
        {
            m_bIsHover = false;
            Invalidate(); //actually my mouse Enter/Leave event
        }

        base.OnMouseLeave(e);
    }

    void CSStackPanelItem_MouseEnter(object sender, EventArgs e)
    {
        m_bIsHover = true;
        Invalidate(); //actually my mouse Enter/Leave event
    }

3
解决方案是跟踪进出数量。 在您的总控制中添加一个计数器:
private int mouseEnterCount = 0;

在MouseEnter处理程序中执行以下操作:
if (++mouseEnterCount == 1)
{
   // do whatever needs to be done when it first enters the control.
}

在MouseLeave处理程序中执行以下操作:
if (--mouseEnterCount == 0)
{
   // do whatever needs to be done when it finally leaves the control.
}

并为所有子控件以及包含对象执行上述MouseEnter和MouseLeave事件处理程序。


3

Matthew的答案并不总是有效。 特别是当子控件设置到其容器的边缘并且鼠标向该方向移动时,您将永远无法检测到MouseLeave事件。

最好的方法是创建一个用户控件容器,然后挂钩所有子控件的MouseEnter和MouseLeave事件,以便您可以正确地检测鼠标何时以及在哪里。 然后,如果它进入到您的容器的边界内,您可以触发自定义的MouseEnter事件,并在离开时触发MouseLeave事件。


1

这可能不是最优雅的解决方案,但您可以在父控制面板(子类面板)中设置一个属性,例如布尔值“bool selected”。然后当面板的MouseEnter事件触发时将其设置为true...然后停止鼠标离开逻辑除非它被设置为false。

示例

bool selected;

MouseEnter(..,..)
{
 if (!selected)
   selected = true;
 else
   selected = false;

  if (selected)
   /.. Logic Here ../
}

MouseLeave()
{
  if (selected)
   return;

/.. Logic Here ../
}

实际上,我只需要在子元素的MouseLeave事件中设置参数即可。
Example:

Parent:
  bool doLeave;

  MouseLeave(..,..)
  {
    if (doLeave)
    {
     /.. Logic ../
     doLeave = false;
  }

Child:
 MouseLeave(..., ...)
  {
    DerivedPanel parent = this.Parent as DerivedPanel;
    if (parent != null)
      parent.doLeave = true;
  }

这两种方法都不够优雅,但可以解决问题。


0

0

这是一个棘手的问题,可靠地编写代码将会很困难。一种想法是在列表中“捕获”3个传入事件,并在列表完成(包含3个所需事件)后执行所需的代码。然后,当您完成执行任何代码(或者可能以相反的方式捕获事件组合)时,您可以清空列表并准备好下一次发生特定组合事件的情况。这不是理想的解决方案,但只是一个想法。

当然,这并不能克服Hans提出的潜在分辨率问题和可能遗漏的事件。也许需要更多的上下文信息。


0

检查子组件...

if (panel1.GetChildAtPoint(panel1.PointToClient(Cursor.Position)) == null)
{
    // mouse leave panel and children
}

0
我的解决方案是为面板和该面板的父窗体创建一个MouseEnter事件。我不绑定任何MouseLeaving事件。
当光标进入面板时,MouseEnter会触发。我可以访问所有面板子控件,什么也不会发生(这就是我想要的)。当我离开面板时,父窗体的MouseEnter会触发。

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