¿Cómo saber si el usuario ha hecho clic en "X" o en el botón "Cerrar"?

94

En MSDN descubrí CloseReason.UserClosingque el usuario había decidido cerrar el formulario, pero supongo que es lo mismo para hacer clic en el botón X o hacer clic en el botón cerrar. Entonces, ¿cómo puedo diferenciar entre estos dos en mi código?

Gracias a todos.

Bohn
fuente
2
¿A qué botón de cierre te refieres?
Brian R. Bondy
por ejemplo, cierre con "ALT + F4"
Bohn
@Oliver No es la misma pregunta.
Ctrl S

Respuestas:

95

Suponiendo que solicita WinForms, puede usar el evento FormClosing () . El evento FormClosing () se activa cada vez que se va a cerrar un formulario.

Para detectar si el usuario hizo clic en X o en su CloseButton, puede obtenerlo a través del objeto del remitente. Intente convertir el remitente como un control de botón, y verifique tal vez por su nombre "CloseButton", por ejemplo.

private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
    if (string.Equals((sender as Button).Name, @"CloseButton"))
        // Do something proper to CloseButton.
    else
        // Then assume that X has been clicked and act accordingly.
}

De lo contrario, nunca he necesitado diferenciar si se hizo clic en X o CloseButton, ya que quería realizar algo específico en el evento FormClosing, como cerrar todos los MdiChildren antes de cerrar MDIContainerForm, o verificar el evento para detectar cambios no guardados. En estas circunstancias, no necesitamos, según yo, diferenciarnos de ninguno de los dos botones.

Cerrar con ALT+ F4también activará el evento FormClosing (), ya que envía un mensaje al formulario que dice que se cierre. Puede cancelar el evento configurando el

FormClosingEventArgs.Cancel = true. 

En nuestro ejemplo, esto se traduciría en

e.Cancel = true.

Observe la diferencia entre los eventos FormClosing () y FormClosed () .

FormClosing ocurre cuando el formulario recibió el mensaje para ser cerrado y verifica si tiene algo que hacer antes de que se cierre.

FormClosed ocurre cuando el formulario está realmente cerrado, es decir, después de que se cierra.

¿Esto ayuda?

Will Marcouiller
fuente
Sí, gracias por la idea de "Cast", había usado esta técnica con Delphi 7 pero olvidé hacer lo mismo en C #
Bohn
De hecho, este es un puerto de Delphi a .NET. =) ¡Me alegro de haber ayudado!
Will Marcouiller
1
Obtengo 'Referencia de objeto no configurada en una instancia de un objeto' cuando uso este código.
Nate S.
33
Esto está mal. No puede enviar el remitente al botón porque es el formulario en sí. Esto arroja una excepción.
Xtro
1
Tenga en cuenta que esta es una respuesta INCORRECTA. Por favor, no vote a favor de esto.
Najeeb
79

La CloseReasonenumeración que encontró en MSDN es solo con el propósito de verificar si el usuario cerró la aplicación, o fue debido a un apagado, o cerrada por el administrador de tareas, etc.

Puedes realizar diferentes acciones, según el motivo, como:

void Form_FormClosing(object sender, FormClosingEventArgs e)
{
    if(e.CloseReason == CloseReason.UserClosing)
        // Prompt user to save his data

    if(e.CloseReason == CloseReason.WindowsShutDown)
        // Autosave and clear up ressources
}

Pero como habrás adivinado, no hay diferencia entre hacer clic en el botón x, o hacer clic con el botón derecho en la barra de tareas y hacer clic en "cerrar", o presionar Alt F4, etc. Todo termina en una CloseReason.UserClosingrazón.

Philip Daubmeier
fuente
11
Usando el estándar Close (); parece desencadenar CloseReason.UserClosing para mí. No estoy seguro de por qué.
Dan W
Encontré esto útil cuando intento bloquear el cierre de un formulario secundario MDI mediante la acción del usuario en el formulario, pero no cuando el padre se está cerrando.
Steve Pettifer
1
Esto no responde a la pregunta, solo enumera más el problema.
Robert Koernke
¿Cómo se vincula el evento al método?
user2924019
43

El botón "X" se registra como DialogResult.Canceltal otra opción es evaluar el DialogResult.

Si tiene varios botones en su formulario, probablemente ya esté asociando diferentes correos electrónicos DialogResulta cada uno y esto le proporcionará los medios para distinguir la diferencia entre cada botón.

(Ejemplo: btnSubmit.DialogResult = DialogResult.OK, btnClose.DialogResult = Dialogresult.Abort)

    public Form1()
    {
        InitializeComponent();

        this.FormClosing += Form1_FormClosing;
    }

    /// <summary>
    /// Override the Close Form event
    /// Do something
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Form1_FormClosing(Object sender, FormClosingEventArgs e)
    {
        //In case windows is trying to shut down, don't hold the process up
        if (e.CloseReason == CloseReason.WindowsShutDown) return;

        if (this.DialogResult == DialogResult.Cancel)
        {
            // Assume that X has been clicked and act accordingly.
            // Confirm user wants to close
            switch (MessageBox.Show(this, "Are you sure?", "Do you still want ... ?", MessageBoxButtons.YesNo, MessageBoxIcon.Question))
            {
                //Stay on this form
                case DialogResult.No:
                    e.Cancel = true;
                    break;
                default:
                    break;
            }
        }
    }
AlexScript
fuente
1
En mi caso, esto es más útil que la respuesta aceptada. Dado que a la 'X' se le asigna DialogResult.Cancel, la asignación de algún otro valor al botón cancelar los distingue fácilmente y maneja las cosas de manera apropiada.
MickeyfAgain_BeforeSalirOfSO
3
Esto no funciona en mi caso. Cuando presione 'X', DialogResultpermanece None. ¿Cual podría ser el problema?
Bhaskar
1
@Bhaskar, cuando cree una instancia de su diálogo, asegúrese de establecer el DialogResult apropiado para cada botón en su diálogo. Proporcioné un ejemplo arriba, pero no creé un bloque de código para mostrar la declaración de diálogo.
AlexScript
@Bhaskar: Presionar Xhace DialogResultcontener Cancel, no None. Asignar Nonea su botón es lo mismo que no establecer su .DialogResultpropiedad en absoluto, y si llama form.Close()desde el controlador de eventos de su botón, form.DialogResultcontendrá Cancel. Solo asignar un valor que no sea Noneoa Canceltodos los botones de cierre de formulario le permitirá hacer la distinción deseada.
mklement0
9

¿Cómo detectar si el formulario se cerró haciendo clic en el botón X o llamando Close()al código?

No puede confiar en el motivo cercano de los argumentos del evento de cierre del formulario, porque si el usuario hace clic en el botón X en la barra de título o cierra el formulario usando Alt + F4 o usa el menú del sistema para cerrar el formulario o el formulario se cierra llamando al Close()método, todo casos anteriores, el motivo de cierre será cerrado por el usuario que no es el resultado deseado.

Para distinguir si el formulario se cerró con el botón X o con el Closemétodo, puede utilizar cualquiera de las siguientes opciones:

  • Manipule, WM_SYSCOMMANDverifique SC_CLOSEy coloque una bandera.
  • Verifique StackTracesi alguno de los marcos contiene una Closellamada al método.

Ejemplo 1 - Mango WM_SYSCOMMAND

public bool ClosedByXButtonOrAltF4 {get; private set;}
private const int SC_CLOSE = 0xF060;
private const int WM_SYSCOMMAND = 0x0112;
protected override void WndProc(ref Message msg)
{
    if (msg.Msg == WM_SYSCOMMAND && msg.WParam.ToInt32() == SC_CLOSE)
        ClosedByXButtonOrAltF4 = true;
    base.WndProc(ref msg);
}
protected override void OnShown(EventArgs e)
{
    ClosedByXButtonOrAltF4 = false;
}   
protected override void OnFormClosing(FormClosingEventArgs e)
{
    if (ClosedByXButtonOrAltF4)
        MessageBox.Show("Closed by X or Alt+F4");
    else
        MessageBox.Show("Closed by calling Close()");
}

Ejemplo 2: comprobación de StackTrace

protected override void OnFormClosing(FormClosingEventArgs e)
{
    if (new StackTrace().GetFrames().Any(x => x.GetMethod().Name == "Close"))
        MessageBox.Show("Closed by calling Close()");
    else
        MessageBox.Show("Closed by X or Alt+F4");
}
Reza Aghaei
fuente
1
Bien hecho. Lástima que llegaste tarde a la fiesta; es difícil competir con las respuestas más antiguas, ya altamente votadas.
mklement0
1
@ mklement0 Espero que los futuros usuarios lo encuentren útil. Publiqué la respuesta porque ninguna de las otras respuestas podría resolver el problema correctamente y es bastante extraño que una pregunta tenga este número de visitas y respuestas altamente votadas (que no funcionan).
Reza Aghaei
7

Determina cuándo cerrar la solicitud si se cierra un formulario (si su solicitud no está adjunta a un formulario específico).

    private void MyForm_FormClosed(object sender, FormClosedEventArgs e)
    {
        if (Application.OpenForms.Count == 0) Application.Exit();
    }
derloopkat
fuente
5

Siempre uso un método de cierre de formulario en mis aplicaciones que detecta alt + x desde mi botón de salida, alt + f4 o se inició otro evento de cierre de formulario. Todas mis clases tienen el nombre de clase definido como cadena privada mstrClsTitle = "grmRexcel"en este caso, un método de salida que llama al método de cierre de formulario y un método de cierre de formulario. También tengo una declaración para el método de cierre de formulario - this.FormClosing = My Form Closing Form Closing method name.

El código para esto:

namespace Rexcel_II
{
    public partial class frmRexcel : Form
    {
        private string mstrClsTitle = "frmRexcel";

        public frmRexcel()
        {
            InitializeComponent();

            this.FormClosing += frmRexcel_FormClosing;
        }

        /// <summary>
        /// Handles the Button Exit Event executed by the Exit Button Click
        /// or Alt + x
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnExit_Click(object sender, EventArgs e)
        {            
            this.Close();        
        }


        /// <summary>
        /// Handles the Form Closing event
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void frmRexcel_FormClosing(object sender, FormClosingEventArgs e)
        {

            // ---- If windows is shutting down, 
            // ---- I don't want to hold up the process
            if (e.CloseReason == CloseReason.WindowsShutDown) return;
            {

                // ---- Ok, Windows is not shutting down so
                // ---- either btnExit or Alt + x or Alt + f4 has been clicked or
                // ---- another form closing event was intiated
                //      *)  Confirm user wants to close the application
                switch (MessageBox.Show(this, 
                                    "Are you sure you want to close the Application?",
                                    mstrClsTitle + ".frmRexcel_FormClosing",
                                    MessageBoxButtons.YesNo, MessageBoxIcon.Question))
                {

                    // ---- *)  if No keep the application alive 
                    //----  *)  else close the application
                    case DialogResult.No:
                        e.Cancel = true;
                        break;
                    default:
                        break;
                }
            }
        }
    }
}
Tiro de salida
fuente
2

Puede intentar agregar un controlador de eventos desde el diseño de esta manera: Abra el formulario en la vista de diseño, abra la ventana de propiedades o presione F4, haga clic en el botón de la barra de herramientas de eventos para ver los eventos en el objeto Form, busque el evento FormClosing en el grupo Behavior y haga doble clic en él. Referencia: https://social.msdn.microsoft.com/Forums/vstudio/en-US/9bdee708-db4b-4e46-a99c-99726fa25cfb/how-do-i-add-formclosing-event?forum=csharpgeneral

Lisa
fuente
1
if (this.DialogResult == DialogResult.Cancel)
        {

        }
        else
        {
            switch (e.CloseReason)
            {
                case CloseReason.UserClosing:
                    e.Cancel = true;
                    break;
            }
        }

si la condición se ejecutará cuando el usuario haga clic en 'X' o en el botón cerrar en el formulario. El else se puede usar cuando el usuario hace clic en Alt + f4 para cualquier otro propósito

phani kiran
fuente
1
namespace Test
{
    public partial class Member : Form
    {
        public Member()
        {
            InitializeComponent();
        }

        private bool xClicked = true;

        private void btnClose_Click(object sender, EventArgs e)
        {
            xClicked = false;
            Close();
        }

        private void Member_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (xClicked)
            {
                // user click the X
            } 
            else 
            {
                // user click the close button
            }
        }
    }
}
Dragan Menoski
fuente
1

Estoy de acuerdo con la DialogResult-Solution como la más sencilla.

Sin embargo, en VB.NET, se requiere encasillado para obtener la CloseReasonpropiedad -Property

    Private Sub MyForm_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing

        Dim eCast As System.Windows.Forms.FormClosingEventArgs
        eCast = TryCast(e, System.Windows.Forms.FormClosingEventArgs)
        If eCast.CloseReason = Windows.Forms.CloseReason.None Then
            MsgBox("Button Pressed")
        Else
            MsgBox("ALT+F4 or [x] or other reason")
        End If

    End Sub
DrMarbuse
fuente
0

También tuve que registrar la función de cierre dentro del método "InitializeComponent ()" del formulario:

private void InitializeComponent() {
// ...
this.FormClosing += FrmMain_FormClosing;
// ...
}

Mi función "FormClosing" es similar a la respuesta dada ( https://stackoverflow.com/a/2683846/3323790 ):

private void FrmMain_FormClosing(object sender, FormClosingEventArgs e) {
    if (e.CloseReason == CloseReason.UserClosing){
        MessageBox.Show("Closed by User", "UserClosing");
    }

    if (e.CloseReason == CloseReason.WindowsShutDown){
        MessageBox.Show("Closed by Windows shutdown", "WindowsShutDown");
    }
}

Una cosa más para mencionar: también hay una función "FormClosed" que ocurre después de "FormClosing". Para utilizar esta función, regístrela como se muestra a continuación:

this.FormClosed += MainPage_FormClosed;

private void MainPage_FormClosing(object sender, FormClosingEventArgs e)
{
// your code after the form is closed
}
IVIike
fuente
0

He hecho algo como esto.

private void Form_FormClosing(object sender, FormClosingEventArgs e)
    {
        if ((sender as Form).ActiveControl is Button)
        {
            //CloseButton
        }
        else
        {
            //The X has been clicked
        }
    }
PachecoDt
fuente