WinForms编程 - 模态和非模态窗体问题

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

图片 http://img225.imageshack.us/img225/1075/modalnonmodalproblem.png


2
你是在询问是否要创建一个仅对另一个表单模态的表单吗? - SLaks
我希望当打开一个非模态窗体#1并且有一些额外的模态子窗体(#2)时,主窗体#0仍然保持响应。 - Povilas
5个回答

13

模态窗体完全按照“模态”的含义执行,它们会禁用应用程序中的所有其他窗口。这非常重要,因为您的程序处于一种相当危险的状态。您有一块代码正在等待对话框关闭。如果那些其他窗口没有被禁用,可能会发生非常糟糕的事情。比如用户可能会再次启动模态对话框,现在您的代码嵌套了两次。或者她可能关闭对话框的所有者窗口,使其突然消失。

如果您在循环内部调用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进行工作。 - Povilas

3
首先想到的是这样做。在启动表单2时,您可以禁用表单1,然后让表单1处理第二个表单的关闭事件以重新启用自己。您不应该使用show dialog打开模态2。
现在请注意,从用户角度来看,这将非常繁琐,您可能需要考虑使用MDI应用程序,将所有窗口放入单个容器中。

0

在我看来,您可以使用一个 MDI 应用程序,并将窗体 #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各自独立,甚至可以是单独的应用程序。从#0获取少量单例对象的数据,其中窗体#1正在更新数据。 - Povilas
半年来一切都很好,但由于新要求的冲击,我现在遇到了这样一种情况:使用一个自定义数据编辑器可能需要几个小时。因此,我认为我可以采用Hans Passant所建议的方法——使用单独的GUI线程,并通过几个单例对象同步数据。 - Povilas

0

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


-2

实际上答案非常简单。尝试一下。

newForm.showDialog();

这将打开一个新的窗体,而父窗体将无法访问。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接