2011-04-18 15 views
8

Estoy creando un cuadro desplegable personalizado, y quiero registrarme cuando se presiona el mouse fuera del cuadro desplegable, para ocultarlo. ¿Es posible detectar un clic fuera de un control? ¿o debería hacer algún mecanismo en el formulario que contiene y verificar si hay algún menú desplegable abierto?¿Hay alguna forma de detectar un clic de ratón fuera de un control de usuario?

user control

+0

¿No podría usar el evento "Leave" de un control? Esto se disparará cuando tu nuevo control pierda enfoque, creo. –

+0

¿Detectando cuándo el menú desplegable pierde el foco, tal vez? ¿Qué sucede si te alejas del control, probablemente también quieras cerrar/ocultar el menú desplegable en ese punto? –

Respuesta

10

Así que finalmente entiendo que solo quiere que se cierre cuando el usuario haga clic fuera de él. En ese caso, el Leave event debería funcionar bien ... Por alguna razón, tuve la impresión de que quería que se cerrara cada vez que movían el mouse fuera de su menú desplegable personalizado. El evento Leave se genera cada vez que el control pierde el foco, y si el usuario hace clic en otra cosa, sin duda perderá el foco, ya que lo que hicieron clic en el foco gana.

La documentación también se dice que este evento cascadas arriba y abajo de la cadena de control según sea necesario:

Los Enter y Leave eventos son jerárquicos y caerá en cascada hacia arriba y abajo por la cadena principal hasta que se alcanza el control adecuado. Por ejemplo, supongamos que tiene un Formulario con dos controles GroupBox y cada control GroupBox tiene un control TextBox. Cuando el cursor se mueve de un cuadro de texto a otro, se genera el evento Leave para el cuadro de texto y el cuadro de grupo, y se genera el evento Enter para el otro cuadro de cuadro y cuadro de texto.

Reemplazar el método de su UserControl OnLeave es la mejor manera de manejar esto:

protected override void OnLeave(EventArgs e) 
{ 
    // Call the base class 
    base.OnLeave(e); 

    // When this control loses the focus, close it 
    this.Hide(); 
} 

Y a continuación, para propósitos de prueba, he creado un formulario que muestra el control de usuario desplegable de comando:

public partial class Form1 : Form 
{ 
    private UserControl1 customDropDown; 

    public Form1() 
    { 
     InitializeComponent(); 

     // Create the user control 
     customDropDown = new UserControl1(); 

     // Add it to the form's Controls collection 
     Controls.Add(customDropDown); 
     customDropDown.Hide(); 
    } 

    private void button1_Click(object sender, EventArgs e) 
    {   
     // Display the user control 
     customDropDown.Show(); 
     customDropDown.BringToFront(); // display in front of other controls 
     customDropDown.Select();   // make sure it gets the focus 
    } 
} 

Todo funciona perfectamente con el código anterior, excepto por una cosa: si el usuario hace clic en un área en blanco del formulario, el UserCont rol no se cierra. Hmm, ¿por qué no? Bueno, porque la forma en sí misma no quiere el foco. Solo los controles pueden enfocarse, y no hicimos clic en un control. Y como nada más le robó el foco, el evento Leave nunca se levantó, lo que significa que el UserControl no sabía que se suponía que debía cerrarse.

Si necesita que el UserControl se cierre cuando el usuario hace clic en un área en blanco en el formulario, necesita algún manejo especial de casos para eso. Ya que dices que eres sólo se preocupa por clics, usted puede controlar el evento Click para el formulario, y establecer el foco a un control diferente:

protected override void OnClick(EventArgs e) 
{ 
    // Call the base class 
    base.OnClick(e); 

    // See if our custom drop-down is visible 
    if (customDropDown.Visible) 
    { 
     // Set the focus to a different control on the form, 
     // which will force the drop-down to close 
     this.SelectNextControl(customDropDown, true, true, true, true); 
    } 
} 

Sí, esta última parte se siente como un hack . La mejor solución, como han mencionado otros, es usar el SetCapture function para indicar a Windows que capture el mouse sobre la ventana de UserControl. El control Capture property proporciona una forma aún más simple de hacer lo mismo.

+0

Gracias, nunca quise que se cerrara cuando el mouse dejó el control del usuario, uno de los carteles sugirió esto como una solución alternativa. Voy a tratar de implementar tu respuesta. Si funciona, esto será perfecto. – Bildsoe

+0

¡Funcionó! Gracias – Bildsoe

2

Técnicamente, necesitará p/invocar SetCapture() para recibir eventos de clic que suceden fuera de su control.

Pero en su caso, manejar el evento Leave, como sugiere @Martin, debería ser suficiente.

EDIT: bien en busca de un ejemplo de uso de SetCapture(), me encontré con la propiedad Control.Capture, de los cuales yo no era consciente. Usar esa propiedad significa que no tendrás que p/invocar nada, lo cual siempre es bueno en mi libro.

Por lo tanto, tendrá que establecer Capture a true cuando se muestra el menú desplegable, a continuación, determinar si el puntero del ratón se encuentra el control en el controlador de eventos clic y, si no lo hace, configurar Capture a false y cerrar la desplegable.

+0

Tengo otros controles en el control de usuario, y cuando estos están enfocados, el evento Leave se activa para usercontrol. – Bildsoe

+0

@Bildsoe, si entiendo correctamente, desea dejar el menú desplegable abierto cuando el usuario hace clic en otro control dentro de su control de usuario, pero ciérrelo si hace clic fuera de su control de usuario. –

+0

@ Frédéric Hamidi: el menú desplegable también debe cerrarse cuando el usuario hace clic en un control dentro de mi control de usuario, pero no cuando el usuario coloca otro control dentro. – Bildsoe

0

He hecho esto yo mismo, y así es como lo hice.

Cuando se abre la gota abajo, registrar un evento click en forma parental del control:

this.Form.Click += new EventHandler(CloseDropDown); 

Pero esto sólo te lleva la mitad del camino. Es probable que también desee que su menú desplegable se cierre cuando la ventana actual se desactiva. La forma más fiable de detectar esto tiene para mí estado a través de un temporizador que comprueba la que se encuentra la ventana activa:

[System.Runtime.InteropServices.DllImport("user32.dll")] 
static extern IntPtr GetForegroundWindow(); 

y

var timer = new Timer(); 
timer.Interval = 100; 
timer.Tick += (sender, args) => 
{ 
    IntPtr f = GetForegroundWindow(); 
    if (this.Form == null || f != this.Form.Handle) 
    { 
     CloseDropDown(); 
    } 
}; 

Usted debe, por supuesto, sólo dejar correr el cronómetro cuando el descenso es visible. Además, es probable que haya algunos otros eventos en los padres forman te gustaría registrar cuando el descenso se abre:

this.Form.LocationChanged += new EventHandler(CloseDropDown); 
this.Form.SizeChanged += new EventHandler(CloseDropDown); 

Eso sí, no se olvide de cancelar el registro de todos estos eventos en el método CloseDropDown :)

EDIT:

me olvidaba, también debe registrar el evento Dejar de controlar para ver si otro control se activa/hace clic en:

this.Leave += new EventHandler(CloseDropDown); 

creo Me'v Ahora lo tengo, esto debería cubrir todas las bases. Avísame si me estoy perdiendo algo.

+1

Hmm, no. No haga sondeos periódicamente para la ventana de primer plano actual. Eso es definitivamente un mal uso de la API. Tienes una manera perfectamente buena de hacer exactamente lo que se te pide aquí. Implica capturar el mouse. Busca 'SetCapture'. –

+0

Excelente sugerencia @Cody, me aseguraré de revisarlo en caso de que necesite actualizar este control. Te recomiendo que eches un vistazo a ti mismo @Bildsoe. También me gustaría señalar que generalmente implemento mi menú desplegable como un formulario separado, por lo que tendrías que tenerlo en cuenta usando el enfoque GetForegroundWindow. –

4

la manija del formulario evento MouseDown, o anular OnMouseDown método del formulario:

enter code here 

Y luego:

protected override void OnMouseDown(MouseEventArgs e) 
{ 

    if (!theListBox.Bounds.Contains(e.Location)) 
    { 
     theListBox.Visible = false; 
    } 
} 

La Contiene viejo método System.Drawing.Rectangle se puede utilizar para indicar si un punto es contenido dentro de un rectángulo. La propiedad Bounds de un control es , el Rectangle externo definido por los bordes del control.La ubicación propiedad del MouseEventArgs es el punto relativo al control que recibió el evento MouseDown. La propiedad Bounds de un Control en un Formulario es relativa al Formulario.

Cuestiones relacionadas