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?
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:
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.
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));
}
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.
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;
}
}