【问题标题】:How do I respond to the last MDI childform closing?如何响应最后一个 MDI 子窗体关闭?
【发布时间】:2016-10-05 00:27:09
【问题描述】:

我的目标是响应 mdi 容器关闭的最后一个子窗体(例如,关闭父级本身或显示新内容)。我面临的问题是 mdi 容器的 MdiChildren 集合仍然表明该容器包含子项。

我尝试过的方法是

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

MdiChildren.Count() 在最后一个子窗体关闭后仍为 1。

我通过尝试处理 parentform.MdiChildActivate 事件得到了相同的结果。

当子窗体关闭时,MdiChildren 集合似乎尚未更新。当有多个孩子时也会发生同样的情况:它仍然会包含所有孩子,它似乎会在稍后更新集合。

这是正确的方法吗?如果没有,我如何才能在关闭表单后准确计算 mdi 孩子的数量?

【问题讨论】:

    标签: c# winforms events mdi


    【解决方案1】:

    是的,直到 FormClosed 事件传递之后,MdiChildren 属性才会更新。像这样的事件顺序问题有一个通用的解决方案,您可以使用 Control.BeginInvoke() 方法优雅地在事件处理完成后运行代码。这段代码解决了你的问题:

    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");
            }
        }));
    }
    

    【讨论】:

    • 虽然我不怀疑这行得通,但它看起来还是有点像 hack。哪个 MDI 子正在被激活,为什么会在最终表单关闭后进行激活?同样,我完全相信它有效,只是说这似乎有点违反直觉,乍一看可能会让开发人员感到困惑。
    • 这是 Windows GUI 程序员使用的一个非常古老的技巧,PostMessage() 是底层机制。从来没有人称它为“黑客”,只是盒子里的另一个工具。它有许多用途,也解决了这个问题。
    • 虽然您可能将其称为技巧,但我会说这是针对底层实现问题的 hack 或解决方法。 Form 对象同时具有 FormClosingFormClosed 事件。 MdiChildren 集合应该在 FormClosing 中包含封闭的子表单,但在 FormClosed 触发时不包含这似乎是合乎逻辑的。行为不是这种方式的事实有点令人困惑,因此这个 SO 问题。您的代码 sn-p 虽然有效,但乍一看并不明显。如果我看到这个我会问,“为什么我们要覆盖 MdiChildActivate 而不是 FormClosed 事件?”
    • 关于“hack”的问题,你必须离开我,我是故意使用“优雅”这个词的。使用 FormClosed 事件并不优雅,忘记订阅它仍然不起作用。 MdiChildActivate 没有问题,只需订阅一次。
    • 如果您冒犯了我的陈述,我深表歉意。我并没有试图将您的代码称为坏的或骇人听闻的。您的实现是很好的代码,它简单且集中。出于您提到的确切原因,我确实喜欢单个重载方法而不是事件处理程序的好处。但是,我觉得您需要这样做因为 WinForms 糟糕的实现。虽然它工作得很好,但它仍然是MdiChildren 的愚蠢实现的解决方法,这是微软的错,而不是你。我的观点只是,您的代码不会立即明确“为什么”是必要的。
    【解决方案2】:

    WinForms 有时可能有点古怪,这就是其中一个例子。我不完全确定为什么关闭最后一个 MDI 子项不会使 MdiChildren 属性在其后立即返回一个空数组。

    在大多数情况下,您将自己跟踪子表单或数据模型,因此我只需保留一个本地数组来管理它:

        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.
            }
        }
    

    【讨论】:

    • 我认为这是更好的方法,因为它不需要读者了解事件顺序来理解代码。不过,mdichildren 列表未更新的问题仍然很奇怪..
    【解决方案3】:

    这是一个完整的测试示例:

    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");
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      您的代码似乎暗示您正在检查子窗体的 MdiChildren 集合。但也许我误读了你的代码。

      使用 MDI 表单的“MdiChildActivate”事件:

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

      【讨论】:

      • 将代码更改为使用事件而不是计时器。好多了。
      • 如前所述,当MdiChildActivate 事件触发时,MdiChildren 集合中仍有项目
      【解决方案5】:

      我知道这个答案很晚了,但我有一个比提供的更简单的问题解决方案,所以我会在这里分享。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-31
        • 1970-01-01
        • 1970-01-01
        • 2014-10-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多