父控制鼠标使用子控件输入/离开事件

我有一个C#.NET 2.0 WinForms应用程序。 我的应用程序有一个控件,它是两个子控件的容器:标签和某种编辑控件。 您可以这样想,外框是父控件:

 + --------------------------------- + 
 |  [标签控制] [编辑控制] |
 + --------------------------------- + 

我试图在鼠标进入或离开父控件时执行某些操作,但我不在乎鼠标是否移动到其中一个子控件中。 我想要一个标志来表示“鼠标在父或子中的某个位置”和“鼠标移动到父控件边界之外”。

我已尝试在父控件和两个子控件上处理MouseEnter和MouseLeave,但这意味着当鼠标移过控件时,操作会多次开始和结束。 换句话说,我明白了:

 Parent.OnMouseEnter(开始做某事)
 Parent.OnMouseLeave(停止)
 Child.OnMouseEnter(开始做某事)
 Child.OnMouseLeave(停止)
 Parent.OnMouseEnter(开始做某事)
 Parent.OnMouseLeave(停止) 

中间的OnMouseLeave事件会导致一些不良影响,因为我正在做的事情开始然后停止。 我想避免这种情况。

我不想捕获鼠标,因为父控件需要鼠标移动,因为子控件需要鼠标事件,我想要菜单和其他快捷键才能工作。

有没有办法在.NET框架内执行此操作? 或者我需要使用Windows鼠标挂钩?

经过更多的研究,我发现了Application.AddMessageFilter方法 。 使用这个,我创建了一个.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事件,检查鼠标是否在我的父控件内,然后开始工作。 (我还必须跟踪最后一个moused over parent控件,以便我可以停止之前启动的父级。)

—-编辑—-

在我的表单类中,我创建并连接filter:

 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; } } 

我觉得我找到了比当前最受欢迎的解决方案更好的解决方案。

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

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

虽然这个解决方案并不完全优雅,但它很简单并且有效:

添加一个透明控件,占用要接收MouseEnter和MouseLeave事件的容器的整个空间。

我在Amed的答案中找到了一个很好的透明控件: 使控件透明

然后我将其删除:

 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; } } 

享受适当的MouseLeave和MouseEnter活动!

您可以像这样找出鼠标是否在控件的范围内(假设此代码驻留在容器控件中;如果没有,请将其替换为对容器控件的引用):

 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 } } 

我不认为你需要挂钩消息泵来解决这个问题。 您的UI中的某些标记应该可以解决问题。 我在想你在你的控制类中创建一个成员变量,比如Control _someParent,当你的一个OnMouseEnter处理程序被调用时,它将引用父控件。 然后,在OnMouseLeave中,检查_someParent“flag”的值,如果它与当前发送者的值相同,则不要实际停止处理,只需返回。 仅当父级不同时,才会停止并将_someParent重置为null。

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

要初始化,请将感兴趣的容器控件传递给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 handles; public ContainerMessageFilter( Control container ) { handles = CollectContainerHandles( container ); } private static IEnumerable CollectContainerHandles( Control container ) { var handles = new List { container.Handle }; RecurseControls( container.Controls, handles ); return handles; } private static void RecurseControls( IEnumerable controls, List 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 ); } }