有没有可能使用ShowDialog不阻止所有窗体?

我希望我能够清楚地解释这一点。 我有我的主要forms(A),它使用form.Show()和第二个子窗体(C)使用form.Show()打开1个子窗体(B)。 现在我想要子窗体B使用form.ShowDialog()打开窗体(D)。 当我这样做,它阻止formsA和formsC. 有没有办法打开一个模式对话框,只有它阻止打开它的forms?

如果您在A和C的单独线程上运行表单B,则ShowDialog调用将仅阻止该线程。 显然,这当然不是一件小事。

只要在单独的线程上运行Form D的ShowDialog调用,就可以让对话框不会阻塞任何线程。 这需要相同types的工作,但要less得多,因为只有一个表单从应用程序的主线程运行。

使用多个GUI线程是棘手的业务,如果这是您唯一的动机,我会build议您不要这样做。

更合适的方法是使用Show()而不是ShowDialog() ,并禁用所有者窗体,直到popup窗体返回。 只有四点考虑:

  1. 当使用ShowDialog(owner) ,popup窗体停留在其所有者之上。 当您使用Show(owner)时也是如此。 或者,您可以显式设置Owner属性,具有相同的效果。

  2. 如果您将所有者窗体的Enabled属性设置为false ,窗体将显示一个禁用状态(子控件被“灰化”),而当使用ShowDialog时,所有者窗体仍然被禁用,但不显示禁用状态。

    当您调用ShowDialog ,所有者窗体在Win32代码中被禁用 – 其WS_DISABLED样式位被设置。 这导致它失去了获得焦点的能力,并在点击时“叮当”,但不会使其变成灰色。

    当您将窗体的Enabled属性设置为false ,会在某些控件绘制自己时设置附加标志(在框架中,而不是基础Win32子系统)。 这个标志告诉控制器将他们自己置于一个禁用状态。

    所以为了模拟ShowDialog会发生什么,我们应该直接设置本地的WS_DISABLED样式位,而不是将窗体的Enabled属性设置为false 。 这是通过一些互操作来完成的:

     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. ShowDialog()调用不会返回,直到对话框被解除。 这很方便,因为您可以在对话框完成业务之前暂停您所有者表单中的逻辑。 Show()调用一定不会这样。 因此,如果您要使用Show()而不是ShowDialog() ,则需要将逻辑分为两部分。 应该在对话框之后运行的代码(包括重新启用所有者窗体)应该由一个Closed事件处理程序运行。

  4. 当窗体显示为对话框时,设置其DialogResult属性会自动closures它。 只要单击具有非NoneDialogResult属性的button,该属性就会被设置。 Show的表单不会像这样自动closures,所以当单击其中一个解除button时,我们必须明确地closures它。 但请注意, DialogResult属性仍由button正确设置。

实现这四件事情,你的代码就像这样:

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

您可以使用一个单独的线程(如下所示),但是这正在进入危险的领域 – 如果您了解线程的影响(同步,跨线程访问等),则只能靠近此选项:

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

(很明显,你不会像上面那样构造代码 – 这只是显示行为的最简单的方式;在真实的代码中,每个表单都有一个类,等等)

我想总结一下可能的解决办法,并增加一个新的select(3a和3b)。 但首先我想澄清一下我们在说什么:

我们有一个应用程序有多种forms。 有一个要求显示模式对话框,将阻止只有我们的forms的某些子集,而不是其他人。 modal dialog可能只显示在一个子集(场景A)或多个子集(场景B)中。

现在总结可能的解决scheme:

  1. 不要使用通过ShowDialog()显示的模式forms

    考虑你的应用程序的devise。 你真的需要使用ShowDialog()方法吗? 如果你不需要模态forms,这是最简单和最干净的方式。

    当然,这个解决scheme并不总是适合的。 ShowDialog()给了我们一些function。 最值得注意的是,它禁用所有者(但不灰色),用户无法与其交互。 爸爸给了我一个非常辛苦的答案。

  2. 仿真ShowDialog()行为

    可以模拟该方法的行为。 我再次推荐阅读P爸爸的答案 。

    a) Form 使用 Enabled属性的组合,并通过Show() Form显示为非模式。 因此,禁用的表单将变灰。 但是它是完全pipe理的解决scheme,不需要任何互操作。

    b)不喜欢父母表格变灰? 参考几个本地方法,并closures父窗体上的WS_DISABLED (再次 – 请参阅P Daddy的答案)。

    这两个解决scheme要求您对需要处理的所有对话框有完全控制权。 你必须使用特殊的结构来显示“部分阻止对话”,不要忘记它。 你需要调整你的逻辑,因为Show()是非阻塞的,而ShowDialog()是阻塞的。 处理系统对话框(文件select器,拾色器等)可能是个问题。 另一方面,你不需要任何额外的代码在表单上不会被对话框阻止。

  3. 克服ShowDialog()限制

    请注意有Application.EnterThreadModalApplication.LeaveThreadModal事件。 只要显示modal dialog,就会引发此事件。 请注意,事件实际上是线程范围的,而不是应用程序范围的。

    a)听取Application.EnterThreadModal事件的表单,不要被对话框阻塞,并打开这些表单中的WS_DISABLED 。 您只需要调整不应被modal dialog阻止的表单。 您可能还需要检查显示的模式窗体的父链,并根据此条件切换WS_DISABLED (在您的示例中,如果还需要以窗体A和窗体C打开对话框,而不是阻止窗体B和窗体D)。

    b)隐藏和重新显示不应被阻止的表单 。 请注意,在显示模式对话框后显示新窗体时,它不会被阻止。 利用这一点,并显示模式对话框时,隐藏并再次显示所需的forms,以便他们不被阻止。 但是这种方法可能会带来一些闪烁。 在理论上可以通过在Win API中启用/禁用重新绘制表单来修复,但我不能保证。

    c)将Owner属性设置为在对话框显示时不应该被阻止的窗体上的对话框窗体 。 我没有testing这个。

    d)使用多个GUI线程 。 回答TheSmurf 。

我只是想在这里添加我的解决scheme,因为它似乎对我很好,可以封装成一个简单的扩展方法。 我唯一需要做的就是处理闪烁的@nightcoder评论@ PDaddy的答案。

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

在FormA的新线程中启动FormB:

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

现在,使用ShowDialog()在新线程中打开的任何窗体将只阻止FormB,而不是FormA或FormC

我正在写一个应用程序时遇到类似的问题。 我的主要用户界面是在主线程上运行的表单。 我有一个帮助对话框,我想作为一个无模式对话框运行。 这很容易实现,甚至到确保我只有一个帮助对话框的实例运行。 不幸的是,我使用的任何modal dialog都会导致帮助对话框失去焦点 – 当这些modal dialog中的某些对话框运行时,有帮助对话框将会非常有用。

使用这里和其他地方提到的想法,我设法克服了这个错误。

我在我的主UI中声明了一个线程。

 Thread helpThread; 

以下代码处理触发的事件以打开帮助对话框。

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

我还需要添加到我的构造函数中的线程的初始化,以确保在第一次运行此代码时不引用空对象。

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

这可以确保线程在任何给定时间只有一个实例。 线程本身运行对话框,并在对话框closures后停止。 由于它在单独的线程上运行,因此在主UI中创buildmodal dialog不会导致帮助对话框挂起。 我确实需要添加

 helpDialog.Abort(); 

到我的主UI的窗体closures事件,以确保在应用程序终止时帮助对话框closures。

我现在有一个无模式的帮助对话框,它不受我主UI中产生的modal dialog的影响,这正是我想要的。 这是安全的,因为在主UI和帮助对话框之间不需要通信。

这里是帮助我在WPF中使用,以防止基于对这个问题的一些答案对话框阻止非对话窗口:

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

用法:

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

使用示例:

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

源代码:

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

也许一个子窗口(请参阅ChildWindow的详细信息)将是一个更优雅的解决scheme,它可以避免GUI的单独线程的所有问题。