Mouse de control principal Ingrese / deje eventos con controles secundarios

Tengo una aplicación C .NET 2.0 WinForms. Mi aplicación tiene un control que es un contenedor para dos controles secundarios: una etiqueta y algún tipo de control de edición. Puedes pensarlo así, donde la caja externa es el control principal:

 + --------------------------------- + 
 |  [Control de etiqueta] [Editar control] |
 + --------------------------------- + 

Intento hacer algo cuando el mouse entra o sale del control principal, pero no me importa si el mouse se mueve a uno de sus elementos secundarios. Quiero una sola bandera para representar “el mouse está en algún lugar dentro del padre o hijos” y “el mouse se ha movido fuera de los límites de control principales”.

He intentado manejar MouseEnter y MouseLeave en el control primario y en ambos controles secundarios, pero esto significa que la acción comienza y finaliza varias veces a medida que el mouse se mueve a través del control. En otras palabras, entiendo esto:

 Parent.OnMouseEnter (empieza a hacer algo)
 Parent.OnMouseLeave (detener)
 Child.OnMouseEnter (empieza a hacer algo)
 Child.OnMouseLeave (detener)
 Parent.OnMouseEnter (empieza a hacer algo)
 Parent.OnMouseLeave (detener) 

Los eventos intermedios OnMouseLeave causan algunos efectos no deseados, ya que lo que estoy haciendo se inicia y se detiene. Quiero evitar eso.

No quiero capturar el mouse cuando el padre pasa el mouse, porque los controles secundarios necesitan sus eventos de mouse, y quiero que el menú y otras teclas de método abreviado funcionen.

¿Hay alguna manera de hacer esto dentro del framework .NET? ¿O necesito usar un gancho de mouse de Windows?

Después de más investigaciones, descubrí el método Application.AddMessageFilter . Al usar esto, creé una versión .NET de un gancho de mouse:

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

Luego, puedo manejar el evento MouseMove en mi control contenedor, verificar si el mouse está dentro de mi control padre e iniciar el trabajo. (También tuve que rastrear el último control moused sobre parent para poder detener el padre iniciado anteriormente.)

—- Editar —-

En mi clase de formulario, creo y conecto el filtro:

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

Siento que encontré una solución mucho mejor que la solución actualmente aceptada.

El problema con otras soluciones propuestas es que son bastante complejas (manejando directamente mensajes de nivel inferior).

O fallan en los casos de esquina: confiar en la posición del mouse en MouseLeave puede hacer que pierda el mouse al salir si el mouse va directamente desde el interior de un control secundario al exterior del contenedor.

Si bien esta solución no es del todo elegante, es sencilla y funciona:

Agregue un control transparente que ocupe todo el espacio del contenedor para el que desea recibir los eventos MouseEnter y MouseLeave.

Encontré un buen control transparente en la respuesta de Amed aquí: hacer un control transparente

Lo que luego desglosé a esto:

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

Ejemplo de uso:

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

¡Disfruta de los eventos MouseLeave y MouseEnter adecuados!

Puede averiguar si el mouse está dentro de los límites de su control de esta manera (suponiendo que este código reside en el control de su contenedor; si no, reemplácelo por una referencia al control del contenedor):

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

No creo que deba enganchar la bomba de mensajes para resolver esto. Algunos indicadores en su UI deberían hacer el truco. Estoy pensando que creas una variable miembro, algo así como Control _someParent, en tu clase de control que tomará la referencia del control padre cuando se llame a uno de tus manejadores OnMouseEnter. Luego, en OnMouseLeave, verifique el valor de _sparent “flag” y si es el mismo que el del remitente actual, entonces no detenga su procesamiento, solo regrese. Solo cuando el padre es diferente se detiene y restablece _someParent en nulo.

Yo tenía exactamente la misma necesidad. La respuesta de Paul Williams me brindó la idea central, pero tuve dificultades para entender el código. Encontré otra toma aquí , y juntos, los dos ejemplos me ayudaron a desarrollar mi propia versión.

Para inicializar, pasa el control de contenedor de interés al constructor ContainerMessageFilter . La clase recoge los identificadores de ventana del contenedor y todos los controles secundarios dentro de él.

Luego, durante la operación, la clase filtra el mensaje WM_MOUSEMOVE , verificando el HWnd los mensajes para determinar en qué control se mueve el mouse. De esta forma, determina cuándo el mouse se ha movido dentro o fuera del conjunto de controles dentro del contenedor que está mirando.

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

En el siguiente ejemplo de uso, queremos monitorear la entrada y salida del mouse para un Panel y los controles secundarios que contiene:

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