C# 是否可以在不阻塞所有表单的情况下使用 ShowDialog?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/428494/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is it possible to use ShowDialog without blocking all forms?
提问by Jon Tackabury
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?
我希望我能解释得足够清楚。我有我的主窗体 (A),它使用 form.Show() 打开 1 个子窗体 (B),使用 form.Show() 打开第二个子窗体 (C)。现在我希望子窗体 B 使用 form.ShowDialog() 打开一个窗体 (D)。当我这样做时,它也会阻塞表单 A 和表单 C。有没有办法打开模态对话框并且只让它阻止打开它的表单?
采纳答案by TheSmurf
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.
如果您在与 A 和 C 不同的线程上运行 Form B,则 ShowDialog 调用将仅阻止该线程。显然,这当然不是一项微不足道的工作投资。
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.
只需在单独的线程上运行 Form D 的 ShowDialog 调用,就可以让对话框完全不阻塞任何线程。这需要相同类型的工作,但要少得多,因为您将只有一个表单从您的应用程序的主线程中运行。
回答by Marc Gravell
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.)
(显然,您实际上不会像上面那样构造代码 - 这只是显示行为的最短方式;在实际代码中,您每个表单都有一个类,等等。)
回答by Robert Venables
Start FormB in a new thread in FormA:
在 FormA 的新线程中启动 FormB:
(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
现在,使用 ShowDialog() 在新线程中打开的任何表单只会阻止 FormB 而不是 FormA 或 FormC
回答by P Daddy
Using multiple GUI threads is tricky business, and I would advise against it, if this is your only motivation for doing so.
使用多个 GUI 线程是一件棘手的事情,如果这是您这样做的唯一动机,我建议您不要这样做。
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:
更合适的方法是使用Show()
而不是ShowDialog()
,并禁用所有者表单,直到弹出表单返回。无外乎四点考虑:
When
ShowDialog(owner)
is used, the popup form stays on top of its owner. The same is true when you useShow(owner)
. Alternatively, you can set theOwner
property explicitly, with the same effect.If you set the owner form's
Enabled
property tofalse
, the form shows a disabled state (child controls are "grayed out"), whereas whenShowDialog
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—itsWS_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 tofalse
, 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 nativeWS_DISABLED
style bit directly, instead of setting the form'sEnabled
property tofalse
. 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. TheShow()
call, necessarily, does not behave this way. Therefore, if you're going to useShow()
instead ofShowDialog()
, 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 aClosed
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 aDialogResult
property other thanNone
is clicked. A form shown withShow
will not automatically close like this, so we must explicitly close it when one of its dismissal buttons is clicked. Note, however, that theDialogResult
property still gets set appropriately by the button.
当
ShowDialog(owner)
使用时,其拥有者的顶部弹出的形式停留。使用Show(owner)
. 或者,您可以Owner
显式设置属性,效果相同。如果您将所有者表单的
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)); }
在
ShowDialog()
关闭对话框之前,调用不会返回。这很方便,因为您可以暂停所有者表单中的逻辑,直到对话框完成其业务。的Show()
通话,必然不行为这种方式。因此,如果您打算使用Show()
而不是ShowDialog()
,您需要将您的逻辑分为两部分。关闭对话框后应运行的代码(包括重新启用所有者表单)应由Closed
事件处理程序运行。当表单显示为对话框时,设置其
DialogResult
属性会自动关闭它。每当单击具有DialogResult
其他属性的按钮时,都会设置此属性None
。显示的表单Show
不会像这样自动关闭,因此我们必须在单击其关闭按钮之一时明确关闭它。但是请注意,该DialogResult
属性仍由按钮适当设置。
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;
}
}
回答by Elgar Storm
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.
我在编写的应用程序中遇到了类似的问题。我的主 UI 是一个在主线程上运行的表单。我有一个帮助对话框,我想作为无模式对话框运行。这很容易实现,甚至可以确保我只运行过一个帮助对话框实例。不幸的是,我使用的任何模态对话框都会导致帮助对话框失去焦点 - 当其中一些模态对话框正在运行时,帮助对话框将是最有用的。
Using ideas mentioned here, and in other places, I managed to overcome this bug.
使用这里和其他地方提到的想法,我设法克服了这个错误。
I declared a thread inside my main UI.
我在主 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
这确保线程在任何给定时间只有一个实例。线程本身运行对话框,并在对话框关闭后停止。由于它在单独的线程上运行,因此从主 UI 内创建模式对话框不会导致帮助对话框挂起。我确实需要添加
helpDialog.Abort();
to the form closing event of my main UI to make sure that the help dialog closes when the application is terminated.
到我的主 UI 的表单关闭事件,以确保帮助对话框在应用程序终止时关闭。
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.
我现在有一个无模式帮助对话框,它不受从我的主 UI 中产生的任何模式对话框的影响,这正是我想要的。这是安全的,因为主 UI 和帮助对话框之间不需要通信。
回答by Justin Pihony
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.
我只是想在这里添加我的解决方案,因为它对我来说似乎很有效,并且可以封装成一个简单的扩展方法。我唯一需要做的就是处理闪烁,因为@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);
}
回答by DermFrench
Maybe a child window (see ChildWindowfor details) would be a more elegant solution, and it would avoid all the problems with separate threads for the GUI.
也许子窗口(有关详细信息,请参阅ChildWindow)将是一个更优雅的解决方案,它可以避免 GUI 的单独线程的所有问题。
回答by mancze
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:
我想总结一下可能的解决方案并添加一个新的替代方案(3a 和 3b)。但首先我想澄清我们在谈论什么:
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).
我们有一个具有多种形式的应用程序。需要显示模态对话框,它只会阻止我们表单的某些子集,而不是其他。模态对话框可能只在一个子集(场景 A)或多个子集(场景 B)中显示。
And now summary of possible solutions:
现在总结一下可能的解决方案:
Don't use modal forms shown via
ShowDialog()
at allThink 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.Emulate
ShowDialog()
behaviorIt is possible to emulate behavior of that mathod. Again I recommend reading P Daddy's answer.
a) Usecombination of
Enabled
property onForm
and showing form as non-modal viaShow()
. 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 andShowDialog()
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.Overcome limitations of
ShowDialog()
Note that there are
Application.EnterThreadModal
andApplication.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
eventin forms which shall not be blocked by dialog and turn onWS_DISABLED
bitin 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 switchWS_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 formon forms which should not be blocked when dialog is shown. I did not test this.d) Use multiple GUI threads. Answer from TheSmurf.
不要使用通过显示模式窗体
ShowDialog()
在所有考虑应用程序的设计。你真的需要使用
ShowDialog()
方法吗?如果您不需要模态形式,这是最简单、最干净的方法。当然,这种解决方案并不总是合适的。有一些功能
ShowDialog()
给了我们。最值得注意的是它禁用了所有者(但不会变灰)并且用户无法与其交互。非常累人的回答提供了P爸爸。模仿
ShowDialog()
行为可以模拟该数学的行为。我再次建议阅读P Daddy 的回答。
a) 使用
Enabled
属性Form
组合并通过 将表单显示为非模态Show()
。因此,禁用的表单将变灰。但它是完全托管的解决方案,不需要任何互操作。b)不喜欢父表单变灰?引用一些本机方法并关闭
WS_DISABLED
父表单上的位(再次 - 请参阅P Daddy 的回答)。这两种解决方案要求您完全控制需要处理的所有对话框。您必须使用特殊构造来显示“部分阻塞对话框”,并且一定不要忘记它。您需要调整您的逻辑,因为它
Show()
是非阻塞的并且ShowDialog()
是阻塞的。处理系统对话框(文件选择器、颜色选择器等)可能是个问题。另一方面,您不需要在不会被对话框阻止的表单上添加任何额外代码。克服限制
ShowDialog()
请注意,有
Application.EnterThreadModal
和Application.LeaveThreadModal
事件。每当显示模态对话框时都会引发此事件。请注意,事件实际上是线程范围的,而不是应用程序范围的。a) 以
Application.EnterThreadModal
不应被对话阻塞的形式侦听事件,并在这些形式中打开WS_DISABLED
位。您只需要调整不应被模态对话框阻止的表单。您可能还需要检查正在显示的模态表单的父链,并WS_DISABLED
根据此条件进行切换(在您的示例中,如果您还需要通过表单 A 和 C 打开对话框但不阻止表单 B 和 D)。b) 隐藏和重新显示不应被阻止的表格。请注意,在显示模态对话框后显示新表单时,它不会被阻止。利用这一点,当显示模式对话框时,隐藏并再次显示所需的表单,以便它们不会被阻止。然而这种方法可能会带来一些闪烁。理论上可以通过启用/禁用 Win API 中的表单重绘来修复它,但我不保证这一点。
c) 将
Owner
属性设置为在显示对话框时不应被阻止的表单上的对话框表单。我没有测试这个。d) 使用多个 GUI 线程。来自 TheSmurf 的回答。
回答by Paul
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));
}
}
回答by ghord
Here is helper I'm using in WPF to prevent dialog from blocking non dialog windows based on some answers to this question:
这是我在 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));
}
}
Usage:
用法:
if(true == window.ShowDialogNonBlocking())
{
// Dialog result has correct value
}