Search code examples
c#winformsevent-handlingchildwindowformclosing

How to detect parent form cancelling a FormClosing event?


I have a child form opened from my main form as follows:

var cf = new ChildForm { Owner = this };
cf.Show();

The child form then does some drawing on another thread.

When a user tries to close the main form - if the child form is open - then a FormClosing event is fired first in ChildForm and then the same event is raised in the main form. On a FormClosing event the child form stops its drawing thread.

Users may try to close the main form when it contains unsaved data. Then they are shown a warning "Data not saved. Cancel close?", by the main form's FormClosing event handler. They can then cancel the save (i.e. the Cancel flag is set on the FormClosingEventArgs object by the main form's FormClosing event handler).

However, by that point, the child form's FormClosing event has already been raised, and it will have stopped its drawing thread. The child form does not know that it should now continue drawing (as if nothing had happened).

Is it possible to detect from the child form that the FormClosing event was cancelled by the main form? I would still like to stop the redrawing thread while the user is being asked to save data in the main form.


Solution

  • I would provide a solution based on interfaces. This way will be easy for you to have an uniform way of managing if the application can be closed or not. With the following implementation the parent-form is in charge of asking the child-window if is ready to be closed, the child does whatever actions has to be done and replies to main window.

    Let suppose I have the interface IManagedForm:

    interface IManagedForm
    {
        bool CanIBeClosed(Object someParams);
    }
    

    Both forms (Form1 and ChildForm) would implement it.

    Note that for this example I'm instantiating the ChildForm in this way:

    ChildForm cf = new ChildForm() { Owner = this, Name = "ChildForm" };
    cf.Show();
    

    Here comes first the implementation of the interface by Form1:

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        object someArgsInterestingForTheMethod = new object();
    
        e.Cancel = !((IManagedForm)this).CanIBeClosed(someArgsInterestingForTheMethod);
    }
    
    // Ask the ChildForm it is done. If not the user should not leave the application.
    public bool CanIBeClosed(object someParams)
    {
        bool isOKforClosing = true;
    
        var cf = this.Controls["ChildForm"] as IManagedForm;
    
        if (cf != null)
        {
            isOKforClosing = cf.CanIBeClosed(someParams);
            if (!isOKforClosing)
            {
                MessageBox.Show("ChildForm does not allow me to close.", "Form1", MessageBoxButtons.OK);
            }
        }
        return isOKforClosing;
    }
    

    And finally your ChildForm implementation of the interface would look like this:

    private void ChildForm_FormClosing(object sender, FormClosingEventArgs e)
    {
        object someArgsInterestingForTheMethod = new object();
    
        e.Cancel = !((IManagedForm)this).CanIBeClosed(someArgsInterestingForTheMethod);
    }
    
    public bool CanIBeClosed(object someParams)
    {
        // This flag would control if this window has not pending changes.
        bool meetConditions = ValidateClosingConditions(someParams);
        // If there were pending changes, but the user decided to not discard
        // them an proceed saving, this flag says to the parent that this form
        // is done, therefore is ready to be closed.
        bool iAmReadyToBeClosed = true;
    
        // There are unsaved changed. Ask the user what to do.
        if (!meetConditions)
        {
            // YES => OK Save pending changes and exit.
            // NO => Do not save pending changes and exit.
            // CANCEL => Cancel closing, just do nothing.
            switch (MessageBox.Show("Save changes before exit?", "MyChildForm", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question))
            {
                case DialogResult.Yes:
                    // Store data and leave...
                    iAmReadyToBeClosed = true;
                    break;
                case DialogResult.No:
                    // Do not store data, just leave...
                    iAmReadyToBeClosed = true;
                    break;
                case DialogResult.Cancel:
                    // Do not leave...
                    iAmReadyToBeClosed = false;
                    break;
            }
        }
        return iAmReadyToBeClosed;
    }
    
    // This is just a dummy method just for testing
    public bool ValidateClosingConditions(object someParams)
    {
        Random rnd = new Random();
        return ((rnd.Next(10) % 2) == 0);
    }
    

    Hope it is clear enough.