2009-07-21 15 views
19

Tengo una aplicación WinForms C# .NET 2.0. 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. Usted puede pensar en él como esta, donde la caja exterior es el control de los padres:Padres control del ratón Enter/Deja eventos con controles secundarios

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

que estoy tratando de hacer algo cuando el ratón entra o sale del control de los padres, pero no me importa si el ratón se mueve en uno de sus hijos. 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 tratado manejo MouseEnter y MouseLeave en el padre y los dos controles secundarios, pero esto significa que la acción comienza y termina en múltiples ocasiones cuando el ratón se mueve a través del control. En otras palabras, me sale esto:

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

Los eventos intermedios OnMouseLeave causar algunos efectos no deseados como lo que estoy haciendo se inicia y luego se detuvo. Quiero evitar eso.

no quiero capturar el ratón como el padre se pone el ratón sobre, debido a que los controles secundarios necesitan que sus eventos de ratón, y quiero menú y otras teclas de acceso directo a trabajar.

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

Respuesta

8

Después de más investigaciones, descubrí el Application.AddMessageFilter method. El uso de este, he creado una versión .NET de un enlace 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); 
    } 
} 

Entonces, puedo controlar el evento MouseMove en mi control contenedor, comprobar para ver si el ratón está dentro de mi control de los padres, y empezar el trabajo . (También tuve que realizar un seguimiento de la última ratón por encima de control de los padres para que pudiera detener la anteriormente empezada padres.)

---- ---- Editar

En mi clase de formulario, creo y Conexión para 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; 
    } 
} 
2

no creo que es necesario conectar el suministro de mensajes para resolver esto. Algunos indicadores en su UI deberían hacer el truco. Estoy pensando que se crea una variable miembro, algo así como el Control _someParent, en la clase de control que tendrá la referencia del control de los padres cuando uno de sus manejadores OnMouseEnter se llama. 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.

+0

Si salgo de la matriz actual, no sé de antemano entraré en un control infantil o realmente me voy del padre. Y si hice lo que sugiere, el padre OnMouseLeave tendría que decir "hey, soy yo, así que continúe procesando", por lo que mudarse fuera del control y no a otro control no detendría el procesamiento. –

+0

Aunque este es un buen punto, si la estructura de contención de control no tiene que ser demasiado flexible, es decir, la contención de nivel ny/o si se tiene en cuenta el tipo principal, se podría seguir utilizando este método. Por ejemplo, obtiene un evento enter, luego toma el control del que procede y sube el árbol de contención hasta que vea un GroupBox o Panel (lo que contenga sus botones) y evalúe la referencia a ese valor. Cuando cambia –

3

Puede averiguar si el ratón está dentro de los límites de su control de esta manera (asumiendo este código reside en su control contenedor; si no, sustituir this con una referencia al control 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 
    } 
} 
+0

Idea interesante, pero parece no funcionar en la práctica. A veces, el último conjunto de eventos para un control primario dado son Child.MouseEnter y Child.MouseLeave. En mi controlador Child.MouseLeave, a veces el Cursor.Position sigue estando dentro de los límites del padre. El código cree que el cursor todavía está adentro, por lo que la acción "el mouse está dentro del padre" nunca se detiene. –

+0

¡Esto funciona genial! Conecté todos los eventos secundarios a los mismos controladores en la clase principal y luego verifiqué la ubicación del mouse. Guardé un booleano que guarda si el mouse está dentro del control (para suprimir el manejo de movimientos de mouse hijo-parental, etc. –

+0

Esto no soluciona lo que ocurre cuando una ventana superior (como el administrador de tareas) está parcialmente arriba del rectángulo de la pantalla.Este código actuará como si el mouse no hubiera salido cuando el mouse ingresa la parte de la ventana superior que cubre el control. – jnm2

5

Creo 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 casos de esquina: confiar en la posición del mouse en MouseLeave puede hacer que pierda el mouse que sale si el mouse va directo desde el interior de un control secundario al exterior del contenedor.

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

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

me encontré con un buen control de la transparencia en la respuesta de Amed aquí: Making a control transparent

que luego despojado a esto: el uso

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:

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

Disfrute MouseLeave adecuada y MouseEnter ¡eventos!

1

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 here, y juntas, los dos ejemplos me ayudaron a desarrollar mi propia versión.

Para inicializar, pase 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 el funcionamiento, la clase filtra el mensaje WM_MOUSEMOVE, verificando los mensajes HWnd 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<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); 
    } 
} 

En el siguiente ejemplo de uso, que quieren controlar la entrada y salida del ratón para un Panel y el niño controla 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); 
    } 
} 
Cuestiones relacionadas