2009-07-21 38 views
19

我有一个C#.NET 2.0 WinForms应用程序。我的应用程序有一个控件,它是两个子控件的容器:标签和某种编辑控件。你可以认为它像这样,在外包装盒是家长控制:家长控制鼠标进入/离开活动与儿童自控

 
+---------------------------------+ 
| [Label Control] [Edit Control] | 
+---------------------------------+

我想,当鼠标进入或离开父控件做一些事情,但我不在乎,如果鼠标移动成为其中的一个孩子。我想要一个标志来表示“鼠标在父或子内部的某个位置”和“鼠标已经移到父控制边界之外”。

我已经试过处理的MouseEnter和鼠标离开的父母和两个孩子的控制,但这意味着行动开始和结束多次作为整个控制鼠标移动。换句话说,我得到这个:

 
Parent.OnMouseEnter  (start doing something) 
Parent.OnMouseLeave  (stop) 
Child.OnMouseEnter  (start doing something) 
Child.OnMouseLeave  (stop) 
Parent.OnMouseEnter  (start doing something) 
Parent.OnMouseLeave  (stop)

中间OnMouseLeave在事件导致一些不想要的效果无论我做得到启动,然后停止。我想避免这种情况。

我不想捕获鼠标作为父在获取鼠标,因为孩子需要控制他们的鼠标事件,我想菜单等快捷键的工作。

有没有办法做到这一点。NET框架内?或者我需要使用Windows鼠标钩子?

回答

8

经过更多的研究,我发现了Application.AddMessageFilter method。利用这一点,我创建了一个鼠标钩子的.NET版本:

class MouseMessageFilter : IMessageFilter, IDisposable 
{ 
    public MouseMessageFilter() 
    { 
    } 

    public void Dispose() 
    { 
     StopFiltering(); 
    } 

    #region IMessageFilter Members 

    public bool PreFilterMessage(ref Message m) 
    { 
     // Call the appropriate event 
     return false; 
    } 

    #endregion 

    #region Events 

    public class CancelMouseEventArgs : MouseEventArgs 
    {...} 

    public delegate void CancelMouseEventHandler(object source, CancelMouseEventArgs e); 
    public event CancelMouseEventHandler MouseMove; 
    public event CancelMouseEventHandler MouseDown; 
    public event CancelMouseEventHandler MouseUp; 

    public void StartFiltering() 
    { 
     StopFiltering(); 
     Application.AddMessageFilter(this); 
    } 

    public void StopFiltering() 
    { 
     Application.RemoveMessageFilter(this); 
    } 
} 

然后,我可以在我的容器控制手柄MouseMove事件,检查是否鼠标是我父控件内,并开始工作。 (我也有跟踪,最后将鼠标悬停在父控件,所以我可以阻止以前启动父。)

---- ----编辑

在我的表单类,我创建并联播过滤器:

public class MyForm : Form 
{ 
    MouseMessageFilter msgFilter; 

    public MyForm() 
    {... 
     msgFilter = new MouseMessageFilter(); 
     msgFilter.MouseDown += new MouseMessageFilter.CancelMouseEventHandler(msgFilter_MouseDown); 
     msgFilter.MouseMove += new MouseMessageFilter.CancelMouseEventHandler(msgFilter_MouseMove); 
    } 

    private void msgFilter_MouseMove(object source, MouseMessageFilter.CancelMouseEventArgs e) 
    { 
     if (CheckSomething(e.Control) 
      e.Cancel = true; 
    } 
} 
2

我不认为你需要挂钩消息泵来解决这个问题。在你的用户界面中进行一些标记应该可以做到。我在想你在控制类中创建一个成员变量,就像Control _someParent一样,当你的OnMouseEnter处理程序被调用时,它将采用父控件的引用。然后,在OnMouseLeave中,检查_someParent“标志”的值,如果它与当前发件人的相同,那么实际上并不会停止处理,只需返回即可。只有当父母不同时才会停止并将_someParent重置为空。

+0

如果我离开当前的父母,我不知道提前我要进入一个孩子控制或真的离开父母。如果我按你的建议做了,那么父母OnMouseLeave必须说“嗨,这是我,所以继续处理”,所以移动到控制之外而不是另一个控制不会停止处理。 – 2009-07-21 22:54:50

+0

这是一个很好的观点,尽管如果您的控制遏制结构不必过于灵活,即n级遏制和/或您考虑了父类型,那么您仍然可以使用此方法。 例如,您会收到一个enter事件,然后您控制它来自哪里,然后向上移动遏制树,直到看到GroupBox或Panel(包含按钮的任何内容)并评估该值的引用。当它改变时。 – 2009-07-22 14:16:16

3

你可以找到鼠标是否在你的控制这样的范围内(假设此代码驻留在你的容器控制;如果不符合,参照容器控件取代this):

private void MyControl_MouseLeave(object sender, EventArgs e) 
{ 
    if (this.ClientRectangle.Contains(this.PointToClient(Cursor.Position))) 
    { 
     // the mouse is inside the control bounds 
    } 
    else 
    { 
     // the mouse is outside the control bounds 
    } 
} 
5

我觉得我找到了比目前最好的解决方案更好的解决方案。

与其他提出的解决方案的问题是,他们要么是相当复杂的(直接处理低级别消息)。

或者他们失败的角落案例:依靠MouseLeave上的鼠标位置可能导致您错过鼠标退出,如果鼠标从子控件内部直接移动到容器外部。

虽然这种解决方案并不完全是优雅的,它是直接和工作原理:

添加一个透明的控制,占用了,你要接收的MouseEnter和鼠标离开事件容器的整个空间。

我发现艾湄的答案有好的透明控制:Making a control transparent

然后我剥了下来,以这样的:

public class TranspCtrl : Control 
{ 
    public TranspCtrl() 
    { 
     SetStyle(ControlStyles.SupportsTransparentBackColor, true); 
     SetStyle(ControlStyles.Opaque, true); 
     this.BackColor = Color.Transparent; 
    } 

    protected override CreateParams CreateParams 
    { 
     get 
     { 
      CreateParams cp = base.CreateParams; 
      cp.ExStyle = cp.ExStyle | 0x20; 
      return cp; 
     } 
    } 
} 

用法示例:

public class ChangeBackgroundOnMouseEnterAndLeave 
{ 
    public Panel Container; 
    public Label FirstLabel; 
    public Label SecondLabel; 

    public ChangeBackgroundOnMouseEnterAndLeave() 
    { 
     Container = new Panel(); 
     Container.Size = new Size(200, 60); 

     FirstLabel = new Label(); 
     FirstLabel.Text = "First Label"; 
     FirstLabel.Top = 5; 

     SecondLabel = new Label(); 
     SecondLabel.Text = "Second Lable"; 
     SecondLabel.Top = 30; 

     FirstLabel.Parent = Container; 
     SecondLabel.Parent = Container; 

     Container.BackColor = Color.Teal; 

     var transparentControl = new TranspCtrl(); 
     transparentControl.Size = Container.Size; 

     transparentControl.MouseEnter += MouseEntered; 
     transparentControl.MouseLeave += MouseLeft; 

     transparentControl.Parent = Container; 
     transparentControl.BringToFront(); 
    } 

    void MouseLeft(object sender, EventArgs e) 
    { 
     Container.BackColor = Color.Teal; 
    } 

    void MouseEntered(object sender, EventArgs e) 
    { 
     Container.BackColor = Color.Pink; 
    } 
} 

public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 

     var test = new ChangeBackgroundOnMouseEnterAndLeave(); 
     test.Container.Top = 20; 
     test.Container.Left = 20; 
     test.Container.Parent = this; 
    } 
} 

享受适当的鼠标离开和的MouseEnter活动!

1

我有完全相同的需求。 Paul Williams的答案为我提供了核心思想,但是我很难理解代码。我发现另一个取here,在一起,这两个例子帮助我开发自己的版本。

要进行初始化,请将感兴趣的容器控件传递给构造函数ContainerMessageFilter。该类收集容器的窗口句柄以及其中的所有子控件。

然后,在操作过程中,该类将过滤WM_MOUSEMOVE消息,检查消息的HWnd以确定鼠标在哪个控件内移动。通过这种方式,它可以确定鼠标何时在其正在观察的容器内部或内部移动。

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Linq; 
using System.Windows.Forms; 

public class ContainerMessageFilter : IMessageFilter { 
    private const int WM_MOUSEMOVE = 0x0200; 

    public event EventHandler MouseEnter; 
    public event EventHandler MouseLeave; 

    private bool insideContainer; 
    private readonly IEnumerable<IntPtr> handles; 

    public ContainerMessageFilter(Control container) { 
     handles = CollectContainerHandles(container); 
    } 

    private static IEnumerable<IntPtr> CollectContainerHandles(Control container) { 
     var handles = new List<IntPtr> { container.Handle }; 

     RecurseControls(container.Controls, handles); 

     return handles; 
    } 

    private static void RecurseControls(IEnumerable controls, List<IntPtr> handles) { 
     foreach (Control control in controls) { 
      handles.Add(control.Handle); 

      RecurseControls(control.Controls, handles); 
     } 
    } 

    public bool PreFilterMessage(ref Message m) { 
     if (m.Msg == WM_MOUSEMOVE) { 
      if (handles.Contains(m.HWnd)) { 
       // Mouse is inside container 
       if (!insideContainer) { 
        // was out, now in 
        insideContainer = true; 
        OnMouseEnter(EventArgs.Empty); 
       } 
      } 
      else { 
       // Mouse is outside container 
       if (insideContainer) { 
        // was in, now out 
        insideContainer = false; 
        OnMouseLeave(EventArgs.Empty); 
       } 
      } 
     } 

     return false; 
    } 

    protected virtual void OnMouseEnter(EventArgs e) { 
     var handler = MouseEnter; 
     handler?.Invoke(this, e); 
    } 

    protected virtual void OnMouseLeave(EventArgs e) { 
     var handler = MouseLeave; 
     handler?.Invoke(this, e); 
    } 
} 

在下面的使用示例中,我们要监视一个Panel鼠标进入和退出与子控件,它包含:

public partial class Form1 : Form { 
    private readonly ContainerMessageFilter containerMessageFilter; 

    public Form1() { 
     InitializeComponent(); 

     containerMessageFilter = new ContainerMessageFilter(panel1); 
     containerMessageFilter.MouseEnter += ContainerMessageFilter_MouseEnter; 
     containerMessageFilter.MouseLeave += ContainerMessageFilter_MouseLeave; 
     Application.AddMessageFilter(containerMessageFilter); 
    } 

    private static void ContainerMessageFilter_MouseLeave(object sender, EventArgs e) { 
     Console.WriteLine("Leave"); 
    } 

    private static void ContainerMessageFilter_MouseEnter(object sender, EventArgs e) { 
     Console.WriteLine("Enter"); 
    } 

    private void Form1_FormClosed(object sender, FormClosedEventArgs e) { 
     Application.RemoveMessageFilter(containerMessageFilter); 
    } 
}