Can I use ShowDialog without locking all forms?

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

+42
c # winforms showdialog
Jan 09 '09 at 15:30
source share
10 answers

If you run form B in a separate thread from A and C, calling ShowDialog will block only that thread. Clearly, this is not a trivial investment of work, of course.

You may have a dialog that does not block any threads, just by running a call to form D ShowDialog in a separate thread. This requires the same work, but much less, since you will only have one form that disconnects from the main application stream.

+11
Jan 09 '09 at 15:36
source share

Using multiple GUI threads is a complex business, and I would advise him if this is your only motivation for this.

A much more suitable approach is to use Show() instead of ShowDialog() and disable the owner form until a pop-up form appears. There are only four questions:

  • When ShowDialog(owner) , the popup form remains on top of its owner. The same can be said when using Show(owner) . Alternatively, you can explicitly set the Owner property with the same effect.

  • If you set the Enabled property of the owner property to false , the form shows the disabled state (child controls are gray), while when using ShowDialog owner form is kept disabled, but does not display the disabled state.

    When you call ShowDialog , the owner form is disabled in Win32 code, and the WS_DISABLED style WS_DISABLED set. This causes him to lose the ability to gain focus and β€œdin” when pressed, but does not make a series of gray.

    When you set the Enabled property of the form false , an additional flag is set (within, and not the subsystem of the Win32 subsystem), which certain controls check when they draw themselves. This flag indicates that the controls are turning themselves off.

    So, to mimic what happens with ShowDialog , we need to set the WS_DISABLED style's own bit directly, instead of setting the Enabled property to false . This is achieved by a tiny interaction:

     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)); } 
  • The call to ShowDialog() does not return until the dialog is rejected. This is convenient because you can pause the logic in your owner form until the dialog does its job. Calling Show() does not necessarily behave this way. Therefore, if you intend to use Show() instead of ShowDialog() , you need to split your logic into two parts. The code that should be run after the dialog is rejected (which includes re-enabling the owner form) must be executed by the Closed event handler.

  • When a form is displayed as a dialog, setting its DialogResult property automatically closes it. This property is set when a button is clicked with a DialogResult property other than None . The form shown with Show does not automatically close this way, so we must explicitly close it when one of its dismiss buttons is pressed. Note, however, that the DialogResult property is still set accordingly with the button.

Implementing these four things, your code will become 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; } } 
+72
Jan 09 '09 at 16:37
source share

You can use a separate stream (as shown below), but it falls into a dangerous territory - you should come closer to this option if you understand the consequences of streaming (synchronization, access to cross-stream, 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, in fact, you would not have structured the code as indicated above - this is just the shortest way to show the behavior, in real code you will have a class for each form, etc.)

+8
Jan 09 '09 at 15:45
source share

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

We have an application that takes several forms. There is a requirement to show a modal dialog box that blocks only certain subsets of our forms, but not others. Modal dialogs can be displayed in only one subset (Scenario A) or several subsets (Scenario B).

And now a summary of possible solutions:

  • Do not use modal forms displayed with ShowDialog() at all

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

    Of course, this solution is not always suitable. There are some functions that ShowDialog() gives us. Most noteworthy is that it disables the owner (but is not grayed out) and the user cannot interact with him. A very debilitating answer was provided by P Daddy .

  • ShowDialog() behavior emulation

    You can emulate the behavior of this mathode. Again, I recommend reading the P Daddy answer .

    a) Use a combination of the Enabled property on Form and display the form as modeless through Show() . As a result, a disabled form will be unavailable. But this is a fully manageable solution without the need for intervention.

    b) Don't like the parent form to be grayed out? Link to several native methods and disable the WS_DISABLED bit in the parent form (again - see the answer from P Daddy ).

    These two solutions require that you have full control over all the dialog boxes that you need to handle. You must use a special design to show a "partially blocking dialog" and should not forget about it. You need to configure your logic, because Show() not blocked, and ShowDialog() blocked. A problem with system dialogs (file selections, color pickers, etc.) May be a problem. On the other hand, you do not need additional code in forms that should not be blocked by dialogue.

  • Overcome the limitations of ShowDialog()

    Note that there is Application.EnterThreadModal and Application.LeaveThreadModal . This event occurs when a modal dialog is displayed. Beware that events are actually threads, not common.

    a) Listen to the Application.EnterThreadModal event in forms that should not be blocked by the dialog and enable the WS_DISABLED bit in these forms. You only need to configure forms that should not be blocked by modal dialogs. You may also need to check the parent chain of the displayed modal form and switch WS_DISABLED based on this condition (in your example, if you also need to open dialogs on forms A and C, but not block forms B and D).

    b) Hide and redisplay forms that should not be blocked . Please note that when you show a new form after displaying a modal dialog, it is not blocked. Take advantage of this and when the modal dialog box is displayed, hide and display the desired forms again so that they are not blocked. However, this approach may cause some flicker. Theoretically, it would be possible to set the redrawing of forms on / off in the Win API, but I can not guarantee this.

    c) Set the Owner property to the dialog form in forms that should not be blocked when the dialog is displayed. I have not tested this.

    d) Use multiple GUI threads . Reply from TheSmurf .

+6
Jan 20 '15 at 15:18
source share

I just wanted to add my solution here as it seems to work well for me and can be encapsulated in a simple extension method. The only thing I need to do is deal with blinking, 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); } 
+4
May 23 '12 at 17:12
source share

Run FormB in a new thread in FormA:

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

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

+3
Jan 09 '09 at 16:12
source share

I had a similar problem in the application I wrote. My main user interface was a form working on a main theme. I had a help dialogue that I wanted to launch as a modeless dialogue. This was easy to implement, even in terms of ensuring that I only have one instance of the help dialog. Unfortunately, any modal dialogs that I used made the help dialog also lose focus - when it was, when some of these modal dialogs were executed, which would be most useful using the help dialog.

Using the ideas mentioned here and elsewhere, I managed to overcome this error.

I declared a thread inside my main interface.

 Thread helpThread; 

The following code is about an event that opens in the help dialog box.

 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 to initialize the thread added to my constructor to make sure that I did not reference the null object when I first ran this code.

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

This ensures that the thread has only one instance at any given time. The thread itself starts the dialog and stops after the dialog closes. Since it works in a separate thread, creating a modal dialog from the main user interface does not cause the help dialog to freeze. I needed to add

 helpDialog.Abort(); 

to the closing event of the form of my main user interface to make sure that the help dialog closes when the application is completed.

Now I have a modeless help dialog that is not affected by any modal dialogs created from my main user interface, and this is exactly what I wanted. This is safe because there is no connection between the main interface and the help dialog.

+2
Mar 29 2018-12-12T00:
source share

Here is the helper that I use in WPF to prohibit a window lock dialog without a dialog 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)); } } 

Using:

 if(true == window.ShowDialogNonBlocking()) { // Dialog result has correct value } 
+1
Mar 16 '17 at 7:10
source share

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

0
Dec 29 '12 at 11:31
source share

Using an example:

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

Source:

 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)); } } 
0
Mar 22 '15 at 14:23
source share



All Articles