WinForms 编程 - 模态和非模态表单问题

Posted

技术标签:

【中文标题】WinForms 编程 - 模态和非模态表单问题【英文标题】:WinForms programming - Modal and Non-Modal forms problem 【发布时间】:2010-05-14 14:18:18 【问题描述】:

我对 C#.NET 下的表单形式有疑问。假设我有主表单#0(见下图)。此表单代表主应用程序表单,用户可以在其中执行各种操作。但是,有时需要打开额外的非模态表单来执行额外的主要应用程序功能支持任务。假设这是图像中的表格#1。在这个 #1 表单上,可能会打开几个额外的模态表单(图像中的 #2 表单),最后,有一个进度对话框显示较长的操作进度和状态,这可能需要几个几分钟到几个小时。问题是主窗体#0 在您关闭所有模式窗体(图像中的#2)之前没有响应。我需要主窗体#0 在这种情况下可以运行。但是,如果您在表单#2 中打开非模态表单,则可以同时使用模态#2 表单和新创建的非模态表单进行操作。我需要主窗体#0 和窗体#1 及其所有子窗体之间的相同行为。是否可以?还是我做错了什么?也许有某种解决方法,我真的不想将所有 ShowDialog 调用更改为 Show...

Image http://img225.imageshack.us/img225/1075/modalnonmodalproblem.png

【问题讨论】:

您是否要求创建一个仅对您的其他表单之一模态的表单? 我希望主窗体 #0 在打开带有一些附加模态子窗体 (#2) 的非模态窗体 #1 时保持响应。 【参考方案1】:

模态表单完全符合“模态”的含义,它们禁用应用程序中的所有其他窗口。这很重要,您的程序处于某种危险的状态。您有一大段代码正在等待对话框关闭。如果没有禁用其他窗口,可能会发生非常糟糕的事情。就像用户可以再次启动模式对话框一样,现在您的代码嵌套了两次。或者她可以关闭对话框的所有者窗口,现在它突然消失了。

如果您在循环中调用 Application.DoEvents(),这些正是您会遇到的问题。这是在不禁用其他窗口的情况下让表单表现模态的一种方法。例如:

    Form2 mDialog;

    private void button1_Click(object sender, EventArgs e) 
        mDialog = new Form2();
        mDialog.FormClosed += (o, ea) => mDialog = null;
        mDialog.Show(this);
        while (mDialog != null) Application.DoEvents();
    

这是危险的

当然最好按照它们的设计方式使用模态表单以避免麻烦。如果您不想要模态表单,那么就不要将其设为模态,请使用 Show() 方法。订阅它的 FormClosing 事件以知道它即将关闭:

    private void button1_Click(object sender, EventArgs e) 
        var frm = new Form2();
        frm.FormClosing += new FormClosingEventHandler(frm_FormClosing);
        frm.Show();
    

    void frm_FormClosing(object sender, FormClosingEventArgs e) 
        var frm = sender as Form2;
        // Do something with <frm>
        //...
    

【讨论】:

感谢您的回答,我想我可以使用单独的 GUI 线程,因为主窗体 #0 和窗体 #1 基本上过着自己的生活,彼此不交互。表格 #1 甚至可以是一个单独的应用程序。我无法将 ShowDialog() 更改为 Show() 因为我需要模态表单,但仅在表单 #1 上下文中。用户无法继续使用表单 #1(仅使用表单 #1),直到从表单 #1 打开的模态表单未关闭。【参考方案2】:

首先想到的是这样的。您可以在启动表单 2 时禁用表单 1,然后让表单 1 处理第二个表单的关闭事件以重新启用自身。您不会使用显示对话框打开模态 2。

现在请记住,从用户的角度来看,这将非常麻烦,您可能会考虑使用 MDI 应用程序将所有窗口放入单个容器中。

【讨论】:

【参考方案3】:

在关闭同一进程空间中的任何模式对话框之前,您的主窗体将不会响应。没有办法解决这个问题。

【讨论】:

【参考方案4】:

在我看来,您可以使用 MDI 应用程序将 Form #0 IsMdiContainer 属性设置为 true。

然后,你可以做类似的事情:

public partial class Form0 
    public Form0 
        InitializeComponent();
        this.IsMdiContainer = true; // This will allow the Form #0 to be responsive while other forms are opened.
    

    private void button1_Click(object sender, EventArgs e) 
        Form1 newForm1 = new Form1();
        newForm1.Parent = this;
        newForm1.Show();
    

如您在问题中所述,使用ShowDialog() 将生成所有表单Modal = true。

根据定义,模态形式是:

当表单以模态显示时,除了模态表单上的对象外,不会发生任何输入(键盘或鼠标单击)。程序必须隐藏或关闭模式表单(通常是为了响应某些用户操作),然后才能输入到另一个表单。模态显示的表单通常用作应用程序中的对话框。

您可以使用此属性 [(Modal)] 来确定您从方法或属性中获得的表单是否已被模态显示。

因此,仅当您需要用户立即提供帮助/交互时,才应使用模态表单。否则,使用模态表单会让人相信您可能走错了方向。

如果您不希望您的主窗体成为 MDI 容器,那么也许使用多线程是一种解决方案,通过一个简单的 BackgroundWorker 类是您想要实现的关键。因此,在我看来,它就像一种设计气味……

除了让您的主表单具有响应性等之外,您还想做什么。 你需要做什么?

解释你必须做什么,我们也许能够完全引导你走向正确的方向,或者至少可能是更好的方向。

【讨论】:

感谢您的回答,我会尽力解释得更好。 MDI 或将 ShowDialog() 更改为 Show() 不会为我解决这个问题,因为我需要模态表单(自定义数据编辑器)并且用户在他没有完成当前表单之前无法继续,但我需要模态仅在表单中工作#1 上下文。在表单#1 上,用户正在处理一些稍后在主表单#0 上需要的数据。基本上,主窗体#0 和窗体#1 过着自己的生活,窗体#1 甚至可以是单独的应用程序。 From #0 是从几个单例对象中获取数据,其中 form #1 正在更新数据。 这一年的半年里一切都很好,但由于新需求的涌入,我遇到了使用一个自定义数据编辑器可能需要几个小时的情况。所以我想我可以使用 Hans Passant 的建议——一个单独的 GUI 线程。并通过几个单例对象同步数据。【参考方案5】:

其实答案很简单。试试

newForm.showDialog();

这将打开一个新表单,而无法访问父表单。

【讨论】:

以上是关于WinForms 编程 - 模态和非模态表单问题的主要内容,如果未能解决你的问题,请参考以下文章

关于创建模态窗口和非模态窗口的研究

VS2010/MFC编程(对话框:模态对话框及其弹出过程)

MFC编程入门之十一(对话框:模态对话框及其弹出过程)

(转载)VS2010/MFC编程入门之十一(对话框:模态对话框及其弹出过程)

magnific popup:以编程方式将内容从弹出表单更改为模态微调器,然后返回表单

【读书笔记4】弹框体系总结:模态弹框和非模态弹框