Jon*_*ury 48 c# showdialog winforms
我希望我能够清楚地解释清楚.我有我的主表单(A),它使用form.Show()打开1个子表单(B),使用form.Show()打开第二个子表单(C).现在我希望子窗体B使用form.ShowDialog()打开一个窗体(D).当我这样做时,它会阻止形式A和C形式.有没有办法打开一个模态对话框,只有它阻止打开它的表单?
P D*_*ddy 86
使用多个GUI线程是一件棘手的事情,如果这是你这样做的唯一动机,我会建议不要这样做.
一种更合适的方法是使用Show()代替ShowDialog()和禁用所有者表单,直到弹出窗体返回.只有四个注意事项:
当ShowDialog(owner)被使用时,弹出的形式停留在其拥有者的顶部.使用时也是如此Show(owner).或者,您可以Owner显式设置属性,效果相同.
如果将所有者表单的Enabled属性设置为false,则表单将显示禁用状态(子控件为"灰显"),而在ShowDialog使用时,所有者表单仍会被禁用,但不会显示禁用状态.
当您调用时ShowDialog,所有者表单在Win32代码中被禁用 - 其WS_DISABLED样式位被设置.这会导致它失去获得焦点的能力,并且在点击时会失去"ding",但不会使它自己变成灰色.
当您将表单的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));
}
Run Code Online (Sandbox Code Playgroud)在ShowDialog()解除对话框之前,调用不会返回.这很方便,因为您可以暂停所有者表单中的逻辑,直到对话框完成其业务.这种Show()呼吁必然不会这样.因此,如果您要使用Show()而不是ShowDialog(),则需要将逻辑分为两部分.解除对话框后应该运行的代码(包括重新启用所有者表单)应该由Closed事件处理程序运行.
当表单显示为对话框时,设置其DialogResult属性会自动关闭它.只要单击具有非DialogResult属性的按钮,就会设置此属性None.显示的表单Show不会自动关闭,因此我们必须在单击其中一个解雇按钮时明确关闭它.但请注意,DialogResult按钮仍然可以正确设置属性.
实现这四件事,您的代码就像:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
Mar*_*ell 11
您可以使用单独的线程(如下所示),但这会进入危险区域 - 如果您了解线程的影响(同步,跨线程访问等),您应该只接近此选项:
[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);
}
Run Code Online (Sandbox Code Playgroud)
(显然,您实际上不会像上面那样构造代码 - 这只是显示行为的最短方式;在实际代码中,每个表单都有一个类,等等)
如果在A和C的单独线程上运行表单B,则ShowDialog调用将仅阻止该线程.显然,这当然不是一项微不足道的工作投资.
只需在单独的线程上运行Form D的ShowDialog调用,就可以让对话框完全阻止任何线程.这需要相同类型的工作,但更少,因为您只有一个表单运行应用程序的主线程.
我只是想在这里添加我的解决方案,因为它似乎对我来说效果很好,并且可以封装成一个简单的扩展方法。我唯一需要做的就是处理 @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);
}
Run Code Online (Sandbox Code Playgroud)
我想总结可能的解决方案并添加一个新的替代方案(3a和3b).但首先我想澄清一下我们在谈论的内容:
我们有一个有多种形式的应用程序.需要显示模态对话框,该对话框仅阻止我们表单的某些子集而不阻止其他表单.模态对话框可以仅在一个子集(场景A)或多个子集(场景B)中显示.
现在总结可能的解决方案:
不要使用通过显示模式窗体ShowDialog()在所有
考虑一下您的应用程序的设计.你真的需要使用ShowDialog()方法吗?如果你不需要模态形式,这是最简单,最干净的方法.
当然,这种解决方案并不总是合适的.有一些功能ShowDialog()给了我们.最值得注意的是它禁用了所有者(但不要灰显),用户无法与之交互.P Daddy提供了非常令人疲惫的答案.
模仿ShowDialog()行为
可以模拟该mathod的行为.我再次建议阅读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的回答.
在 FormA 的新线程中启动 FormB:
(new System.Threading.Thread(()=> {
(new FormB()).Show();
})).Start();
Run Code Online (Sandbox Code Playgroud)
现在,使用 ShowDialog() 在新线程中打开的任何表单只会阻止 FormB 而不是 FormA 或 FormC