Is it possible to use ShowDialog without blocking all forms?

C#WinformsShowdialog

C# Problem Overview


I hope I can explain this clearly enough. I have my main form (A) and it opens 1 child form (B) using form.Show() and a second child form (C) using form.Show(). Now I want child form B to open a form (D) using form.ShowDialog(). When I do this, it blocks form A and form C as well. Is there a way to open a modal dialog and only have it block the form that opened it?

C# Solutions


Solution 1 - C#

Using multiple GUI threads is tricky business, and I would advise against it, if this is your only motivation for doing so.

A much more suitable approach is to use Show() instead of ShowDialog(), and disable the owner form until the popup form returns. There are just four considerations:

  1. When ShowDialog(owner) is used, the popup form stays on top of its owner. The same is true when you use Show(owner). Alternatively, you can set the Owner property explicitly, with the same effect.

  2. If you set the owner form's Enabled property to false, the form shows a disabled state (child controls are "grayed out"), whereas when ShowDialog is used, the owner form still gets disabled, but doesn't show a disabled state.

When you call ShowDialog, the owner form gets disabled in Win32 code—its WS_DISABLED style bit gets set. This causes it to lose the ability to gain the focus and to "ding" when clicked, but doesn't make it draw itself gray.

When you set a form's Enabled property to false, an additional flag is set (in the framework, not the underlying Win32 subsystem) that certain controls check when they draw themselves. This flag is what tells controls to draw themselves in a disabled state.

So to emulate what would happen with ShowDialog, we should set the native WS_DISABLED style bit directly, instead of setting the form's Enabled property to false. This is accomplished with a tiny bit of interop:

    const int GWL_STYLE   = -16;
    const int WS_DISABLED = 0x08000000;

    [DllImport("user32.dll")]
    static extern int GetWindowLong(IntPtr hWnd, int nIndex);

    [DllImport("user32.dll")]
    static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

    void SetNativeEnabled(bool enabled){
        SetWindowLong(Handle, GWL_STYLE, GetWindowLong(Handle, GWL_STYLE) &
            ~WS_DISABLED | (enabled ? 0 : WS_DISABLED));
    }

3. The ShowDialog() call doesn't return until the dialog is dismissed. This is handy, because you can suspend the logic in your owner form until the dialog has done its business. The Show() call, necessarily, does not behave this way. Therefore, if you're going to use Show() instead of ShowDialog(), you'll need to break your logic into two parts. The code that should run after the dialog is dismissed (which would include re-enabling the owner form), should be run by a Closed event handler.

  1. When a form is shown as a dialog, setting its DialogResult property automatically closes it. This property gets set whenever a button with a DialogResult property other than None is clicked. A form shown with Show will not automatically close like this, so we must explicitly close it when one of its dismissal buttons is clicked. Note, however, that the DialogResult property still gets set appropriately by the button.

Implementing these four things, your code becomes something like:

class FormB : Form{
    void Foo(){
        SetNativeEnabled(false); // defined above
        FormD f = new FormD();
        f.Closed += (s, e)=>{
            switch(f.DialogResult){
            case DialogResult.OK:
                // Do OK logic
                break;
            case DialogResult.Cancel:
                // Do Cancel logic
                break;
            }
            SetNativeEnabled(true);
        };
        f.Show(this);
        // function Foo returns now, as soon as FormD is shown
    }
}

class FormD : Form{
    public FormD(){
        Button btnOK       = new Button();
        btnOK.DialogResult = DialogResult.OK;
        btnOK.Text         = "OK";
        btnOK.Click       += (s, e)=>Close();
        btnOK.Parent       = this;

        Button btnCancel       = new Button();
        btnCancel.DialogResult = DialogResult.Cancel;
        btnCancel.Text         = "Cancel";
        btnCancel.Click       += (s, e)=>Close();
        btnCancel.Parent       = this;

        AcceptButton = btnOK;
        CancelButton = btnCancel;
    }
}

Solution 2 - C#

You can use a separate thread (as below), but this is getting into dangerous territory - you should only go near this option if you understand the implications of threading (synchronization, cross-thread access, etc.):

[STAThread]
static void Main() {
    Application.EnableVisualStyles();
    Button loadB, loadC;
    Form formA = new Form {
        Text = "Form A",
        Controls = {
            (loadC = new Button { Text = "Load C", Dock = DockStyle.Top}),
            (loadB = new Button { Text = "Load B", Dock = DockStyle.Top})
        }
    };
    loadC.Click += delegate {
        Form formC = new Form { Text = "Form C" };
        formC.Show(formA);
    };
    loadB.Click += delegate {
        Thread thread = new Thread(() => {
            Button loadD;
            Form formB = new Form {
                Text = "Form B",
                Controls = {
                    (loadD = new Button { Text = "Load D",
                        Dock = DockStyle.Top})
                }
            };
            loadD.Click += delegate {
                Form formD = new Form { Text = "Form D"};
                formD.ShowDialog(formB);
            };
            formB.ShowDialog();  // No owner; ShowDialog to prevent exit
        });
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
    };
    Application.Run(formA);
}

(Obviously, you wouldn't actually structure the code like the above - this is just the shortest way of showing the behavior; in real code you'd have a class per form, etc.)

Solution 3 - C#

If you run Form B on a separate thread from A and C, the ShowDialog call will only block that thread. Clearly, that's not a trivial investment of work of course.

You can have the dialog not block any threads at all by simply running Form D's ShowDialog call on a separate thread. This requires the same kind of work, but much less of it, as you'll only have one form running off of your app's main thread.

Solution 4 - C#

I would like to summarize possible solutions and add one new alternatives (3a and 3b). But first I want to clarify what we are talking about:

We have an application which have multiple forms. There is a requirement to show modal dialog which would block only certain subset of our forms but not the others. Modal dialogs may be shown only in one subset (scenario A) or multiple subsets (scenario B).

And now summary of possible solutions:

  1. Don't use modal forms shown via ShowDialog() at all

Think about design of your application. Do you really need to use ShowDialog() method? If you don't need having modal form it's the easiest and the cleanest way to go.

Of course this solution is not always suitable. There are some features which ShowDialog() gives us. The most notable is that it disables the owner (but do not grays out) and user cannot interact with it. The very exhausting answer provided P Daddy.

  1. Emulate ShowDialog() behavior

It is possible to emulate behavior of that mathod. Again I recommend reading P Daddy's answer.

a) Use combination of Enabled property on Form and showing form as non-modal via Show(). As a result disabled form will be grayed out. But it's completely managed solution without any interop needed.

b) Don't like the parent form being grayed out? Reference few native methods and turn off WS_DISABLED bit on parent form (once again - see answer from P Daddy).

Those two solutions require that you have complete control on all the dialog boxes you need to handle. You have to use special construct to show "partially blocking dialog" and must not forget about it. You need to adjust your logic because Show() is non-blocking and ShowDialog() is blocking. Dealing with system dialogs (file choosers, color pickers, etc.) could be problem. On the other hand you do not need any extra code on the forms which shall not be blocked by dialog.

  1. Overcome limitations of ShowDialog()

Note that there are Application.EnterThreadModal and Application.LeaveThreadModal events. This event is raised whenever modal dialog is shown. Beware that events are actually thread-wide, not application-wide.

a) Listen to the Application.EnterThreadModal event in forms which shall not be blocked by dialog and turn on WS_DISABLED bit in those forms. You only need to adjust forms which should not be blocked by modal dialogs. You may also need to inspect the parent-chain of the modal form being shown and switch WS_DISABLED based on this condition (in your example if you also needed to open dialogs by forms A and C but not to block forms B and D).

b) Hide and re-show forms which should not be blocked. Note that when you show new form after modal dialog is shown it is not blocked. Take advantage of that and when modal dialog is shown, hide and show again desired forms so that they are not blocked. However this approach may bring some flickering. It could be theoretically fixed by enabling/disabling repaint of forms in Win API but I do not guarantee that.

c) Set Owner property to dialog form on forms which should not be blocked when dialog is shown. I did not test this.

d) Use multiple GUI threads. Answer from TheSmurf.

Solution 5 - C#

Start FormB in a new thread in FormA:

        (new System.Threading.Thread(()=> {
            (new FormB()).Show();
        })).Start();

Now, any forms opened in the new thread using ShowDialog() will only block FormB and NOT FormA or FormC

Solution 6 - C#

I just wanted to add my solution here as it seems to work well for me, and can be encapsulated into a simple extension method. The only thing I need to do is deal with the flashing as @nightcoder commented on @PDaddy's answer.

public static void ShowWithParentFormLock(this Form childForm, Form parentForm)
{
  childForm.ShowWithParentFormLock(parentForm, null);
}

public static void ShowWithParentFormLock(this Form childForm, Form parentForm, Action actionAfterClose)
{
  if (childForm == null)
    throw new ArgumentNullException("childForm");
  if (parentForm == null)
    throw new ArgumentNullException("parentForm");
  EventHandler activatedDelegate = (object sender, EventArgs e) =>
  {
    childForm.Focus();
    //To Do: Add ability to flash form to notify user that focus changed
  };
  childForm.FormClosed += (sender, closedEventArgs) =>
    {
      try
      {
        parentForm.Focus();
        if(actionAfterClose != null)
          actionAfterClose();
      }
      finally
      {
        try
        {
          parentForm.Activated -= activatedDelegate;
          if (!childForm.IsDisposed || !childForm.Disposing)
            childForm.Dispose();
        }
        catch { }
      }
    };
  parentForm.Activated += activatedDelegate;
  childForm.Show(parentForm);
}

Solution 7 - C#

I was facing a similar problem in an application I was writing. My main UI was a form running on the main thread. I had a help dialog that I wanted to run as a modeless dialog. This was easy to implement, even to the point of ensuring that I only ever had one instance of the help dialog running. Unfortunately, any modal dialogs I used caused the help dialog to lose focus as well - when it was while some of these modal dialogs were running that having the help dialog there would be most useful.

Using ideas mentioned here, and in other places, I managed to overcome this bug.

I declared a thread inside my main UI.

Thread helpThread;

The following code deals with the event fired to open the help dialog.

private void Help(object sender, EventArgs e)
{
    //if help dialog is still open then thread is still running
    //if not, we need to recreate the thread and start it again
    if (helpThread.ThreadState != ThreadState.Running)
    {
        helpThread = new Thread(new ThreadStart(startHelpThread));
        helpThread.SetApartmentState(ApartmentState.STA);
        helpThread.Start();
    }
}

void startHelpThread()
{
    using (HelpDialog newHelp = new HelpDialog(resources))
    {
        newHelp.ShowDialog();
    }
}

I also needed the initialization of the thread added into my constructor to make sure that I was not referencing a null object the first time this code is run.

public MainWindow()
{
    ...
    helpThread = new Thread(new ThreadStart(startHelpThread));
    helpThread.SetApartmentState(ApartmentState.STA);
    ...
}

This makes sure that the thread has only one instance at any given time. The thread itself runs the dialog, and stops once the dialog is closed. Since it runs on a separate thread, creating a modal dialog from within the main UI does not cause the help dialog to hang. I did need to add

helpDialog.Abort();

to the form closing event of my main UI to make sure that the help dialog closes when the application is terminated.

I now have a modeless help dialog which is not affected by any modal dialogs spawned from within my main UI, which is exactly what I wanted. This is safe since there is no communication needed between the main UI and the help dialog.

Solution 8 - C#

Here is helper I'm using in WPF to prevent dialog from blocking non dialog windows based on some answers to this question:

public static class WindowHelper
{
    public static bool? ShowDialogNonBlocking(this Window window)
    {
        var frame = new DispatcherFrame();

        void closeHandler(object sender, EventArgs args)
        {
            frame.Continue = false;
        }

        try
        {
            window.Owner.SetNativeEnabled(false);
            window.Closed += closeHandler;
            window.Show();

            Dispatcher.PushFrame(frame);
        }
        finally
        {
            window.Closed -= closeHandler;
            window.Owner.SetNativeEnabled(true);
        }
        return window.DialogResult;
    }

    const int GWL_STYLE = -16;
    const int WS_DISABLED = 0x08000000;

    [DllImport("user32.dll")]
    static extern int GetWindowLong(IntPtr hWnd, int nIndex);

    [DllImport("user32.dll")]
    static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

    static void SetNativeEnabled(this Window window, bool enabled)
    {
        var handle = new WindowInteropHelper(window).Handle;
        SetWindowLong(handle, GWL_STYLE, GetWindowLong(handle, GWL_STYLE) &
            ~WS_DISABLED | (enabled ? 0 : WS_DISABLED));
    }
}

Usage:

if(true == window.ShowDialogNonBlocking())
{
    // Dialog result has correct value
}

Solution 9 - C#

Maybe a child window (see ChildWindow for details) would be a more elegant solution, and it would avoid all the problems with separate threads for the GUI.

Solution 10 - C#

Using Example:

(new NoneBlockingDialog((new frmDialog()))).ShowDialogNoneBlock(this);

Source code:

class NoneBlockingDialog
{
    Form dialog;
    Form Owner;

    public NoneBlockingDialog(Form f)
    {
        this.dialog = f;
        this.dialog.FormClosing += new FormClosingEventHandler(f_FormClosing);
    }

    void f_FormClosing(object sender, FormClosingEventArgs e)
    {
        if(! e.Cancel)
            PUtils.SetNativeEnabled(this.Owner.Handle, true);
    }

    public void ShowDialogNoneBlock(Form owner)
    {
        this.Owner = owner;
        PUtils.SetNativeEnabled(owner.Handle, false);
        this.dialog.Show(owner);
    }
}

partial class PUtils
{
            const int GWL_STYLE = -16;
    const int WS_DISABLED = 0x08000000;


    [DllImport("user32.dll")]
    static extern int GetWindowLong(IntPtr hWnd, int nIndex);


    [DllImport("user32.dll")]
    static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);


    static public void SetNativeEnabled(IntPtr hWnd, bool enabled)
    {
        SetWindowLong(hWnd, GWL_STYLE, GetWindowLong(hWnd, GWL_STYLE) & ~WS_DISABLED | (enabled ? 0 : WS_DISABLED));
    }
}

Solution 11 - C#

I came to this thread looking to do something similar: have multiple independent parent forms, running in the same process, each with their own modal dialog. An analogy, which I planned to use as proof of it being possible, would be multiple Word documents, each with their own Find-and-Replace window.

I was sure I'd done this before, but upon trying it, well what do you know, even Microsoft Word can't do this. It behaves exactly as the OP described: once you open a modal dialog (like Find and Replace using Ctrl+H), ALL Word documents become blocked and can't be interacted with. Worse still, only clicking the original parent document causes the modal dialog to flash. The rest of them just don't respond, and give no hint as to why they're blocked or what needs to be closed.

This could indeed be a very confusing user experience. I find it surprising that this is the case with Microsoft's own flagship office software (and that I haven't noticed it before). However, it also helped me come to terms with my own application behaving the same way.

Further confirmation comes from the .NET documentation itself, which states that "ShowDialog shows the window, disables all other windows in the application, and returns only when the window is closed."


I realize this isn't technically a "solution". Those who really feel their application needs to support fully-independent parent forms can try the workaround by @PDaddy, which seems very thorough.

However, hopefully this gives a little peace of mind to those who (like me) were just trying to be conscientious with their UI and thought they were doing something wrong. You're not crazy. Apparently, this is just how Windows works.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJon TackaburyView Question on Stackoverflow
Solution 1 - C#P DaddyView Answer on Stackoverflow
Solution 2 - C#Marc GravellView Answer on Stackoverflow
Solution 3 - C#TheSmurfView Answer on Stackoverflow
Solution 4 - C#manczeView Answer on Stackoverflow
Solution 5 - C#Robert VenablesView Answer on Stackoverflow
Solution 6 - C#Justin PihonyView Answer on Stackoverflow
Solution 7 - C#Elgar StormView Answer on Stackoverflow
Solution 8 - C#ghordView Answer on Stackoverflow
Solution 9 - C#DermFrenchView Answer on Stackoverflow
Solution 10 - C#PaulView Answer on Stackoverflow
Solution 11 - C#DRoamView Answer on Stackoverflow