如何从无模式窗体显示模式对话框?
How to show a modal dialog from a modeless form?
我有两个"modeless"表格:
- 一个是特殊的MainForm
- 另一种是无模式形式
您可以看到:
- 两者都存在于任务栏上
- 都有一个任务栏按钮
- 两者都可以独立最小化
- 两者都可以独立恢复
- 没有一个总是在另一个之上(拥有)
现在显示模态表单
从这个非模态形式,我想展示一个模态形式:
Modal 表单被构建为:
var
frmExchangeConfirm: TfrmExchangeConfirm;
begin
frmExchangeConfirm := TfrmExchangeConfirm.Create(Application);
try
//Setting popupMode and popupParent still makes the MainForm disabled
// frmExchangeConfirm.PopupMode := pmExplicit;
// frmExchangeConfirm.PopupParent := Self; //owned by us
frmExchangeConfirm.OwnerForm := Self; //tell the form which owner to use
frmExchangeConfirm.ShowModal;
finally
frmExchangeConfirm.Free;
end;
通过新的 OwnerForm
属性:
告知模态表单使用哪个所有者
protected
procedure SetOwnerForm(const Value: TForm);
public
property OwnerForm: TForm read GetOwnerForm write SetOwnerForm;
end;
强制重新创建句柄:
procedure TfrmExchangeConfirm.SetOwnerForm(const Value: TForm);
begin
FOwnerForm := Value;
if Self.HandleAllocated then
Self.RecreateWnd;
end;
然后是第二次通过CreateParams
:
procedure TfrmExchangeConfirm.CreateParams(var Params: TCreateParams);
begin
inherited;
if FOwnerForm <> nil then
Params.WndParent := FOwnerForm.Handle;
end;
问题是:
- 一旦显示此拥有的模态窗体,我就无法与 MainForm
交互
- 我无法使用任务栏按钮
最小化 MainForm
- 我无法使用任务栏按钮最小化模态框或其拥有的父项
- 如果我使用 最小化 按钮最小化模态窗体,MainForm 会消失
- 我可以使用任务栏按钮激活 MainForm;但我无法与之互动
在过去的十年里,我问过这个问题大约 7 次。上次有人答应我将主窗体设为 MainForm 会解决所有问题。
奖励:自 .NET 1.0 以来,WinForms 已正确处理此问题。
关于什么是模态对话框存在很多混淆。当您必须在继续使用其所有者之前与其进行交互时,对话框是模态的。来自 Windows Interface Design Guidelines:
Dialog boxes have two fundamental types:
- Modal dialog boxes require users to complete and close before continuing with the owner window. These dialog boxes are best used for critical or infrequent, one-off tasks that require completion before continuing.
- Modeless dialog boxes allow users to switch between the dialog box and the owner window as desired. These dialog boxes are best used for frequent, repetitive, on-going tasks.
Windows 有一个 "owner" 的概念。当 window 是 "owned" 时,它将始终出现在其所有者的顶部。当一个window为"modal"时,表示所有者在模态任务完成之前处于禁用状态。
你会在 ProgressDialog
API:
中看到这个效果
HRESULT StartProgressDialog(
[in] HWND hwndParent,
IUnknown *punkEnableModless,
DWORD dwFlags,
LPCVOID pvReserved
);
hwndParent [in]
Type: HWND
A handle to the dialog box's parent window.
dwFlags
Type: DWORD
PROGDLG_MODAL
The progress dialog box will be modal to the window specified by hwndParent. By default, a progress dialog box is modeless.
当然,你可能是刻薄的,并禁用所有其他 windows
- 在线程中
- 过程
- 或系统
但我想要 正确的 行为。我想做:
- Windows 做什么
- Office 应用程序的作用
- Beyond Compare 的作用
- WinForms 的作用
- WPF 的作用
- 我用过的每个应用程序的作用
- 以及任何用户的期望
我从 1998 年起就希望在我的 Delphi 应用程序中使用它;当意识到 Delphi 3 没有正确支持 Windows 95 和任务栏时。
ShowModal
禁用同一线程中的所有其他顶级 windows。这包括您的主要形式。
您必须巧妙地显示此表单以使其按照您想要的方式运行。执行以下操作:
- 禁用无模式所有者表单。
- 通过调用
Show
. 显示 "modal" 表单
- 当"modal"窗体关闭时,启用无模式所有者。确保在销毁 "modal" 表单的 window 之前启用所有者,如下所述。
您可能 运行 在第 2 步和第 3 步之间有自己的模态消息循环,就像 ShowModal
那样,但这可能有点矫枉过正。我只想显示无模式的表单,但禁用其所有者以使其相对于该所有者 "modal" 。
这个过程有点微妙。查看 ShowModal
的来源以获取灵感。此外,雷蒙德关于模态的史诗系列文章是必不可少的阅读材料。我 link 到这里:Why does a MessageBox not block the Application on a synchronized thread?
还有更多来自 Raymond 的内容:The correct order for disabling and enabling windows:
When you destroy the modal dialog, you are destroying the window with foreground activation. The window manager now needs to find somebody else to give activation to. It tries to give it to the dialog's owner, but the owner is still disabled, so the window manager skips it and looks for some other window, somebody who is not disabled.
That's why you get the weird interloper window.
The correct order for destroying a modal dialog is
- Re-enable the owner.
- Destroy the modal dialog.
This time, when the modal dialog is destroyed, the window manager looks to the owner and hey this time it's enabled, so it inherits activation.
No flicker. No interloper.
我有两个"modeless"表格:
- 一个是特殊的MainForm
- 另一种是无模式形式
您可以看到:
- 两者都存在于任务栏上
- 都有一个任务栏按钮
- 两者都可以独立最小化
- 两者都可以独立恢复
- 没有一个总是在另一个之上(拥有)
现在显示模态表单
从这个非模态形式,我想展示一个模态形式:
Modal 表单被构建为:
var
frmExchangeConfirm: TfrmExchangeConfirm;
begin
frmExchangeConfirm := TfrmExchangeConfirm.Create(Application);
try
//Setting popupMode and popupParent still makes the MainForm disabled
// frmExchangeConfirm.PopupMode := pmExplicit;
// frmExchangeConfirm.PopupParent := Self; //owned by us
frmExchangeConfirm.OwnerForm := Self; //tell the form which owner to use
frmExchangeConfirm.ShowModal;
finally
frmExchangeConfirm.Free;
end;
通过新的 OwnerForm
属性:
protected
procedure SetOwnerForm(const Value: TForm);
public
property OwnerForm: TForm read GetOwnerForm write SetOwnerForm;
end;
强制重新创建句柄:
procedure TfrmExchangeConfirm.SetOwnerForm(const Value: TForm);
begin
FOwnerForm := Value;
if Self.HandleAllocated then
Self.RecreateWnd;
end;
然后是第二次通过CreateParams
:
procedure TfrmExchangeConfirm.CreateParams(var Params: TCreateParams);
begin
inherited;
if FOwnerForm <> nil then
Params.WndParent := FOwnerForm.Handle;
end;
问题是:
- 一旦显示此拥有的模态窗体,我就无法与 MainForm 交互
- 我无法使用任务栏按钮 最小化 MainForm
- 我无法使用任务栏按钮最小化模态框或其拥有的父项
- 如果我使用 最小化 按钮最小化模态窗体,MainForm 会消失
- 我可以使用任务栏按钮激活 MainForm;但我无法与之互动
在过去的十年里,我问过这个问题大约 7 次。上次有人答应我将主窗体设为 MainForm 会解决所有问题。
奖励:自 .NET 1.0 以来,WinForms 已正确处理此问题。
关于什么是模态对话框存在很多混淆。当您必须在继续使用其所有者之前与其进行交互时,对话框是模态的。来自 Windows Interface Design Guidelines:
Dialog boxes have two fundamental types:
- Modal dialog boxes require users to complete and close before continuing with the owner window. These dialog boxes are best used for critical or infrequent, one-off tasks that require completion before continuing.
- Modeless dialog boxes allow users to switch between the dialog box and the owner window as desired. These dialog boxes are best used for frequent, repetitive, on-going tasks.
Windows 有一个 "owner" 的概念。当 window 是 "owned" 时,它将始终出现在其所有者的顶部。当一个window为"modal"时,表示所有者在模态任务完成之前处于禁用状态。
你会在 ProgressDialog
API:
HRESULT StartProgressDialog( [in] HWND hwndParent, IUnknown *punkEnableModless, DWORD dwFlags, LPCVOID pvReserved );
hwndParent [in]
Type: HWND
A handle to the dialog box's parent window.dwFlags
Type: DWORD
PROGDLG_MODAL
The progress dialog box will be modal to the window specified by hwndParent. By default, a progress dialog box is modeless.
当然,你可能是刻薄的,并禁用所有其他 windows
- 在线程中
- 过程
- 或系统
但我想要 正确的 行为。我想做:
- Windows 做什么
- Office 应用程序的作用
- Beyond Compare 的作用
- WinForms 的作用
- WPF 的作用
- 我用过的每个应用程序的作用
- 以及任何用户的期望
我从 1998 年起就希望在我的 Delphi 应用程序中使用它;当意识到 Delphi 3 没有正确支持 Windows 95 和任务栏时。
ShowModal
禁用同一线程中的所有其他顶级 windows。这包括您的主要形式。
您必须巧妙地显示此表单以使其按照您想要的方式运行。执行以下操作:
- 禁用无模式所有者表单。
- 通过调用
Show
. 显示 "modal" 表单
- 当"modal"窗体关闭时,启用无模式所有者。确保在销毁 "modal" 表单的 window 之前启用所有者,如下所述。
您可能 运行 在第 2 步和第 3 步之间有自己的模态消息循环,就像 ShowModal
那样,但这可能有点矫枉过正。我只想显示无模式的表单,但禁用其所有者以使其相对于该所有者 "modal" 。
这个过程有点微妙。查看 ShowModal
的来源以获取灵感。此外,雷蒙德关于模态的史诗系列文章是必不可少的阅读材料。我 link 到这里:Why does a MessageBox not block the Application on a synchronized thread?
还有更多来自 Raymond 的内容:The correct order for disabling and enabling windows:
When you destroy the modal dialog, you are destroying the window with foreground activation. The window manager now needs to find somebody else to give activation to. It tries to give it to the dialog's owner, but the owner is still disabled, so the window manager skips it and looks for some other window, somebody who is not disabled.
That's why you get the weird interloper window.
The correct order for destroying a modal dialog is
- Re-enable the owner.
- Destroy the modal dialog.
This time, when the modal dialog is destroyed, the window manager looks to the owner and hey this time it's enabled, so it inherits activation.
No flicker. No interloper.