Search code examples
c#winformsformclosing

delay Application Close best practice?


Is there a better way to handle the task of doing something after the user has chosen to exit a WinForms program than this :

[edit 1 : in response to comment by 'NoBugz] In this case there is no ControlBox on the Form, and there is a reason for putting one level of indirection in what happens when the user chooses to close the Form [/edit 1]

[edit 2 : in response to all comments as of GMT +7 18:35 January 20 ] Perhaps using fading out the MainForm is a simple illustration of what you might want do as the Application is being closed : the user cannot interact with that : it is self-evidently related to the user's decision to terminate the application. [/edit 2]

(use some form of threading ?) (implications for a multi-threaded app ?) (does this code "smell bad" ?)

    // 'shutDown is an external form-scoped boolean variable
    //
    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        // make sure we don't block Windows ShutDown
        // or other valid reasons to close the Form
        if (e.CloseReason != CloseReason.ApplicationExitCall) return;

        // test for 'shutDown flag set here
        if (shutDown) return;

        // cancel closing the Form this time through
        e.Cancel = true;

        // user choice : default = 'Cancel
        if (MessageBox.Show("Exit Application ?", "", MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == System.Windows.Forms.DialogResult.OK)
        {
            // user says Exit : activate Timer, set the flag to detect Exit
            timer1.Enabled = true;
            shutDown = true;
        }
    }

Summary : In a very standard WinForms application (one MainForm launched in Program.cs in the standard way) : in the FormClosing Event handler of the MainForm :

  1. exit immediately (triggering the default behavior : which is to close the MainForm and exit the Application) if :

    a. the CloseReason is anything other CloseReason.ApplicationExitCall

    b. if a special boolean variable is set to true, or

  2. if no immediate exit : cancel the "first call" to FormClosing.

  3. the user then makes a choice, via MessageBox.Show dialog, to Exit the Application, or Cancel :

    a. if the user Cancels, of course, the Application stays "as is."

    b. if the user has chosen to 'Exit :

    1. set the special boolean flag variable to true

    2. run a Timer that does some special stuff.

    3. when the internal test in the Timer code detects the "special stuff" is done, it calls Application.Exit


Solution

  • My suggestions, both as a developer and a user:

    A very fast task

    • Just do the task in the Closing event handler.

    A less fast, but not incredibly slow task

    • Create a non-background thread (so it's not shut down when the application exits) with the task in the Closing event handler.
    • Let the application exit. Forms will go away, et cetera, but the process will keep running until the task is done.
    • Just remember to handle exceptions and such in that worker thread. And make sure that things doesn't crash if the user reopens your application before that task is done.

    Slower tasks

    • In the Closing event handler, open a shutting-down-form and let the form itself close.
    • Do the task in the/behind the shutting-down-form while displaying some friendly progress and information.
    • Exit application when task is done.

    Some untested example code. We are doing something similar to this in one of our applications. The task in our case is to store window properties (location, size, window state, et cetera) to a database.

    private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
    {
        // If it wasn't the user who asked for the closing, we just close
        if (e.CloseReason != CloseReason.UserClosing)
            return;
    
        // If it was the user, we want to make sure he didn't do it by accident
        DialogResult r = MessageBox.Show("Are you sure you want this?", 
                                         "Application is shutting down.",
                                         MessageBoxButtons.YesNo, 
                                         MessageBoxIcon.Question);
        if (r != DialogResult.Yes)
        {
            e.Cancel = true;
            return;
        }
    }
    
    protected override void OnFormClosed(FormClosedEventArgs e)
    {
        // Start the task
        var thread = new Thread(DoTask)
        {
            IsBackground = false,
            Name = "Closing thread.",
        };
        thread.Start();
    
        base.OnFormClosed(e);
    }
    
    private void DoTask()
    {
        // Some task to do here
    }