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

9

我有两个“无模式”窗体:

  • 一个是特殊的MainForm
  • 另一个是无模式窗体

enter image description here

你可以看到:

  • 两者都存在于任务栏上
  • 两者都有任务栏按钮
  • 两者可以独立最小化
  • 两者可以独立还原
  • 两者都不是始终位于其他窗口之上(拥有)

现在展示一个模式窗体

从这个无模式窗体中,我想展示一个模式窗体:

enter image description here

这个模式窗体被构建为:

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 就可以解决所有问题。

奖励: WinForms自.NET 1.0以来已正确处理此问题。

关于模态对话框,存在很多混淆。在Windows界面设计指南中提到:

对话框有两种基本类型:

  • 模态对话框要求用户在继续使用 所有者窗口之前完成并关闭。这些对话框最适用于需要在继续之前完成的关键或不频繁的一次性任务。
  • 非模态对话框允许用户根据需要在对话框和 所有者窗口之间切换。这些对话框最适用于频繁、重复、持续进行的任务。

Windows有一个“所有者”的概念。当一个窗口被“拥有”时,它将始终显示在其所有者上方。当一个窗口是“模态”时,这意味着直到模态任务完成,所有者才会被禁用。

您可以在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一样
  • 像Office应用程序一样
  • 像Beyond Compare一样
  • 像WinForms一样
  • 像WPF一样
  • 像我使用过的每个应用程序一样
  • 以及任何用户期望的一样
自从1998年意识到Delphi 3没有正确支持Windows 95和任务栏以来,我就一直希望在我的Delphi应用程序中实现这一点。

1
当模态窗口显示时,框架是否禁用主表单?这就是你需要停止发生的事情。 - David Heffernan
1
我猜你是从研究 DisableTaskWindows 的操作开始的。 - David Heffernan
5
这是“模态”的意思,它禁用与相同线程的所有形式的交互,除了模态窗口。我猜你和承诺这种设置会通过模态工作的人之间存在误解。无论如何,不要使用showmodal,只需禁用您想要禁用的窗口即可。 - Sertac Akyuz
1
@SertacAkyuz 模态窗口的目的不是禁用所有其他窗口,而是禁用窗口的所有者(http://blogs.msdn.com/b/oldnewthing/archive/2011/01/21/10118482.aspx)。 - Ian Boyd
2
该文档提到了一个之前的文档,其中包括作者自己的定义:“从最终用户的角度来看,当用户开始一个任务后,如果唯一的逃脱方式是取消整个操作,那么就会出现情态。” - Sertac Akyuz
显示剩余16条评论
1个回答

7
ShowModal禁用了同一线程中的所有其他顶级窗口,包括您的主窗体。
您需要微调此窗体的显示方式,使其按照您的意愿行事。请执行以下操作:
  1. 禁用模式窗体所有者窗体。
  2. 通过调用Show显示“模态”窗体。
  3. 当“模态”窗体关闭时,启用模式窗体所有者。确保在“模态”窗体的窗口被销毁之前启用所有者,如下所述。
在步骤2和3之间,您可能会自己运行自己的模态消息循环,如ShowModal所做的那样,但这可能过于冗长。我只会将窗体显示为无模式,但禁用其所有者,以使其相对于该所有者成为“模态”。
这个过程有点微妙。寻找ShowModal源代码来获取灵感。Raymond的模态系列文章是必不可少的阅读材料。我在这里提供链接:为什么在同步线程上,MessageBox不会阻止应用程序? 还有更多来自Raymond的内容:禁用和启用窗口的正确顺序

当您销毁模态对话框时,您正在销毁具有前台激活的窗口。现在窗口管理器需要找到其他人来提供激活。它试图将其提供给对话框所有者,但所有者仍然被禁用,因此窗口管理器跳过它并查找一些其他窗口,即没有被禁用的人。

这就是为什么你得到奇怪的闯入者窗口。

销毁模态对话框的正确顺序是

  • 重新启用所有者。
  • 销毁模态对话框。

这次,当模态对话框被销毁时,窗口管理器会寻找所有者,并且嘿,这次它已经启用了,所以它继承了激活。

不闪烁。没有闯入者。


2
当我向Sertac评论模态UI是什么时,Raymond的旧文章,特别是关于小心禁用和启用的顺序的问题,一直在我的脑海中。我想根本上Delphi不执行Windows概念中的模态对话框。我真的很讨厌不得不自己实现所有这些内容;我只是假设随着Delphi 2009的伟大转变,所有这些错误都已经解决了。 :( - Ian Boyd
1
编写自己的ShowModal是我面临的任务;但我足够有经验知道我会做错。特别是当涉及到像在接收WM_QUIT时需要的特殊情况时。有其他人在Delphi中解决过这个问题吗?@ZoëPeterson,请回答! - Ian Boyd
1
注释掉 DisableTaskWindows / EnableTaskWindows 然后祈求最好的结果? - Ian Boyd
2
编写一个类助手。传入一个要禁用的窗口的开放数组。使用与ShowModal相同的代码。将DisableTaskWindows / EnableTaskWindows替换为禁用/启用提供的窗口的代码。工作完成。 - David Heffernan
1
我打算将其建模为Windows/WinForms:.ShowModal(OwnerWnd: HWND)。然后只需确保正确禁用和启用即可。 - Ian Boyd
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接