如何从无模式窗体显示模式对话框?

How to show a modal dialog from a modeless form?

我有两个"modeless"表格:

您可以看到:

现在显示模态表单

从这个非模态形式,我想展示一个模态形式:

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;

问题是:

在过去的十年里,我问过这个问题大约 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

但我想要 正确的 行为。我想做:

我从 1998 年起就希望在我的 Delphi 应用程序中使用它;当意识到 Delphi 3 没有正确支持 Windows 95 和任务栏时。

ShowModal 禁用同一线程中的所有其他顶级 windows。这包括您的主要形式。

您必须巧妙地显示此表单以使其按照您想要的方式运行。执行以下操作:

  1. 禁用无模式所有者表单。
  2. 通过调用 Show.
  3. 显示 "modal" 表单
  4. 当"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.