Question

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.

Was it helpful?

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.

OTHER TIPS

Well, the desicional logic of the either Yes/NO/Cancel move to some cetral class.
And either FormClosing of Child and Parent call the same function.

Naturally for good user experience you have to manage it in a way that it's executed only once event if the function called from both FormClosing functions.

MSDN explains the FormClosing event as an event that is called before the form closes. It goes on to say that for MDIs, all the child form FormClosing event are called before the parent form FormClosing event is called.

So this means, when the close button is pressed on the main form, it raises a FormClosing event for all child forms. So the child form should determine whether it is ready to be closed, and set the cancel property accordingly. When the event is raised for the main form, it should already be set to cancel.

The main form should not decide for the child form, even though the close event was raised on the main form.

For further reads,

Explanation on Form.FormClosing event at MSDN

Explaining how the cancel property can be used

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top