views:

360

answers:

4

I have a problem with modality of the forms under C#.NET. Let's say I have main form #0 (see the image below). This form represents main application form, where user can perform various operations. However, from time to time, there is a need to open additional non-modal form to perform additional main application functionality supporting tasks. Let's say this is form #1 in the image. On this #1 form there might be opened few additional modal forms on top of each other (#2 form in the image), and at the end, there is a progress dialog showing a long operation progress and status, which might take from few minutes up to few hours. The problem is that the main form #0 is not responsive until you close all modal forms (#2 in the image). I need that the main form #0 would be operational in this situation. However, if you open a non-modal form in form #2, you can operate with both modal #2 form and newly created non modal form. I need the same behavior between the main form #0 and form #1 with all its child forms. Is it possible? Or am I doing something wrong? Maybe there is some kind of workaround, I really would not like to change all ShowDialog calls to ShowDialog...

Image

+2  A: 

The first thing that comes to mind would be something like this. You could disable form 1 when you launch form 2 and then have form 1 handle the closed event of the second form to re-enable itself. You would NOT open modal 2 using show dialog.

Now keep in mind, from a user perspective this is going to be quite cumbersome, you might look at doing a MDI application to get all windows inside of a single container.

Mitchel Sellers
A: 

Your main form will not be responsive until any modal dialogs that are in the same process space are closed. There is not work around for that.

Malcolm Post
+1  A: 

Modal forms do exactly what "modal" means, they disable all other windows in the app. That's rather important, your program is in a somewhat perilous state. You've got a chunk of code that is waiting for the dialog to close. Really Bad Things could happen if those other windows were not disabled. Like the user could start the modal dialog again, now your code is nested twice. Or she could close the owner window of the dialog, now it suddenly disappears.

These are the exact kind of problems you'd run into if you call Application.DoEvents() inside a loop. Which is one way to get a form to behave modal without disabling other windows. For example:

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

This is dangerous. A somewhat safer way is to create a form that should never be modal on another thread:

    private void button1_Click(object sender, EventArgs e) {
        var t = new System.Threading.Thread(() => Application.Run(new Form2()));
        t.SetApartmentState(System.Threading.ApartmentState.STA);
        t.Start();
    }

The problem you'll have now is that this form object has a life of its own, it tends to disappear behind the window of another application, you can't make it owned by the main window. You'll also have to do the typical song-and-dance with Control.Begin/Invoke() to deliver events to another form class.

These are not great solutions. Use modal forms the way they were designed to stay out of trouble. If you don't want a modal form then simply don't make it modal, use the Show() method. Subscribe to its FormClosing event to know that it is about to close:

    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>
        //...
    }
Hans Passant
Thank You for your answer, I think I can use a separate GUI thread, because main form #0 and form #1 basically lives their own lives and do not interact with each other. Form #1 could be even a separate application. I can not change ShowDialog() to Show() because I need modal forms but only in form #1 context. User can not proceed work with form #1 (only with form #1) until modal form opened from form #1 is not closed.
Povilas
A: 

It looks to me like you could use an MDI application setting the Form #0 IsMdiContainer property to true.

Then, you could do something alike:

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

Using the ShowDialog() as you stated in your question will make all of the forms Modal = true.

By definition, a modal form is:

When a form is displayed modally, no input (keyboard or mouse click) can occur except to objects on the modal form. The program must hide or close a modal form (usually in response to some user action) before input to another form can occur. Forms that are displayed modally are typically used as dialog boxes in an application.

You can use this property [(Modal)] to determine whether a form that you have obtained from a method or property has been displayed modally.

So, a modal form shall be used only when you require immediate assistance/interaction from the user. Using modal forms otherwise makes believe that you're perhaps running into a wrong direction.

If you do not want your main form to be an MDI container, then perhaps using multithreading is one solution through a simple BackgroundWorker class is the key to what you want to achieve. Thus, it looks to me like a design smell...

  • What is it you want to do, apart of making your main form responsive, etc.
  • What is it you have to do?

Explaining what you have to do, we might be able to guide you altogether into the right, or at least perhaps better, direction.

Will Marcouiller
Thank You for your answer, I will try to explain better.MDI or changing ShowDialog() to Show() will not solve this for me, because I need modal forms (custom data editors) and user can not proceed until he's not finished with the current one, but I need modality to work only in form #1 context. On form #1 users are working with some data that later is needed on the main form #0. Basically, main form #0 and form #1 lives their own lives, and form #1 could be even separate application. From #0 is taking data from few singleton objects, where form #1 is updating the data.
Povilas
For half of the year everything was fine, but because of new requirements rushing in I have the situation where working with one custom data editor could take up to few hours. So I think I can use what Hans Passant is suggesting - a separate GUI thread. And synchronize data via few singleton objects.
Povilas