0
votes

My goal is to respond to the last child form of an mdi container closing(for example, close the parent itself or show something new). The problem I face is that the mdi container's MdiChildren collection still indicates that the container contains children.

The approach I have tried is

void childMdiForm_FormClosed(object sender, FormClosedEventArgs e)
{
    if (this.MdiChildren.Any())
    {
        //Do stuff
    }
}

MdiChildren.Count() is still 1 after the last child form is closed.

I have the same results by trying to handle the parentform.MdiChildActivate event.

The MdiChildren collection appears to not be updated yet when the child form has closed. The same occurs when there are multiple children: it will still contain all the crildren, it appears to update the collection at a later moment.

Is this the right approach? If not, how can I get an accurate count of the number of mdi children after closing a form?

5

5 Answers

3
votes

Yes, the MdiChildren property doesn't get updated until after the FormClosed event is delivered. There's a universal solution for event order issues like this, you can elegantly get code to run after event handing is completed by using the Control.BeginInvoke() method. This code solves your problem:

protected override void OnMdiChildActivate(EventArgs e) {
    base.OnMdiChildActivate(e);
    this.BeginInvoke(new Action(() => {
        if (this.MdiChildren.Length == 0) {
            // Do your stuff
            //...
            MessageBox.Show("None left");
        }
    }));
}
2
votes

WinForms can be a little quirky at times and this is one example of that. I am not entirely sure why closing the last MDI child does not make the MdiChildren property return an empty array immediately thereafter.

In most cases you're going to be keeping track of either the children forms or the data models yourself, so I would simply keep a local array for managing this:

    List<Form> childForms = new List<Form>();

    void AddChildWindow()
    {
        var window = new ChildForm();
        window.MdiParent = this;
        window.Tag = Guid.NewGuid();

        window.FormClosed += (sender, e) => { OnMdiChildClosed(sender as Form, e.CloseReason); };
        window.Shown += (sender, e) => { OnMdiChildShown(sender as Form); };

        window.Show();
    }

    void OnMdiChildShown(Form window)
    {
        childForms.Add(window);

        Trace.WriteLine(string.Format("Child form shown: {0}", window.Tag));
        Trace.WriteLine(string.Format("Number of MDI children: {0}", childForms.Count));
    }

    void OnMdiChildClosed(Form window, CloseReason reason)
    {
        childForms.Remove(window);

        Trace.WriteLine(string.Format("Child form closed: {0} (reason: {1})", window.Tag, reason));
        Trace.WriteLine(string.Format("Number of MDI children: {0}", childForms.Count));

        if (childForms.Count == 0)
        {
             // Do your logic here.
        }
    }
1
votes

Here's a full test example:

namespace WindowsFormsApplication1
{
    public partial class mdiMainForm : Form
    {
        private List<Form> _children = new List<Form>();

        public mdiMainForm()
        {
            InitializeComponent();
        }

        private void mdiMainForm_Shown(Object sender, EventArgs e)
        {
            Form3 f3 = new Form3();
            f3.MdiParent = this;
            f3.FormClosed += mdiChildClosed;
            _children.Add(f3);
            f3.Show();
            Form4 f4 = new Form4();
            f4.MdiParent = this;
            f4.FormClosed += mdiChildClosed;
            _children.Add(f4);
            f4.Show();
            Form5 f5 = new Form5();
            f5.MdiParent = this;
            f5.FormClosed += mdiChildClosed;
            _children.Add(f5);
            f5.Show();
        }

        private void mdiChildClosed(Object sender, FormClosedEventArgs e)
        {
            if (_children.Contains((Form)sender))
                _children.Remove((Form)sender);
            if (_children.Count == 0)
                MessageBox.Show("all closed");
        }
    }
}
0
votes

You're code seems to imply you are checking the child Form's MdiChildren collection. But perhaps I'm misreading your code.

Use the MDI Form's 'MdiChildActivate' event:

private void Form1_MdiChildActivate(object sender, EventArgs e) {
    if(this.MdiChildren.Length == 0) {
        // replace with your "Do Stuff" code
        MessageBox.Show("All Gone");
    }
}
0
votes

I know this answer is very late, but I have a much simpler solution to the problem than what has been provided so I'll share it here.

    private void ChildForm_FormClosing(object sender, FormClosingEventArgs e)
    {
        // If WinForms will not remove the child from the parent, we will
        ((Form)sender).MdiParent = null;
    }