【问题标题】:How to show a modal dialog from a modeless form?如何从无模式表单显示模式对话框?
【发布时间】:2015-10-20 18:06:05
【问题描述】:

我有两种“无模式”形式:

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

你可以看到:

  • 两者都存在于任务栏上
  • 都有一个任务栏按钮
  • 两者都可以独立最小化
  • 两者都可以独立恢复
  • 两者都不总是在另一个之上(拥有)

现在显示一个模态表单

从这个无模式的表格中,我想展示一个有模式的:

模态形式被构造为:

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
  • 我无法使用任务栏按钮最小化 Modal 或其所属的父级
  • 如果我使用 Minimize 按钮最小化模态窗体,MainForm 就会消失
  • 我可以使用它的任务栏按钮激活MainForm;但我无法与之互动

在过去十年中,我已经问过这个问题大约 7 次。上次我被承诺将主窗体设为 MainForm 将解决所有问题。

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

关于什么是模态对话框存在很多混淆。当您必须与它交互才能继续使用它的所有者时,对话框是模态的。来自Windows Interface Design Guidelines

对话框有两种基本类型:

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

Windows 有一个“所有者”的概念。当一个窗口被“拥有”时,它总是会出现在它的所有者之上。当一个窗口是"modal"时,它意味着所有者被禁用,直到模态任务完成。

您可以在ProgressDialog API 中看到这种效果:

HRESULT StartProgressDialog(
  [in] HWND     hwndParent,
       IUnknown *punkEnableModless,
       DWORD    dwFlags,
       LPCVOID  pvReserved
);

hwndParent [in]
类型:HWND
对话框的父窗口的句柄。

dwFlags
类型:DWORD
PROGDLG_MODAL
进度对话框将模态到由 hwndParent 指定的窗口。默认情况下,进度对话框是无模式的。

当然,你可能很刻薄,并禁用所有其他窗口

  • 在线程中
  • 过程
  • 或系统

但我希望有 正确的 行为。我想做:

  • Windows 的作用
  • Office 应用程序的作用
  • Beyond Compare 的作用
  • WinForms 的作用
  • WPF 的作用
  • 我用过的每个应用程序都做了什么
  • 以及任何用户的期望

自 1998 年以来,我一直希望在我的 Delphi 应用程序中使用它;当意识到 Delphi 3 不能正确支持 Windows 95 和任务栏时。

【问题讨论】:

  • 显示模态窗口时,框架是否禁用主窗体?这就是你需要停止发生的事情。
  • 我想你先看看 DisableTaskWindows 是如何运作的
  • 这就是模态的意思,它禁止与同一个线程的所有形式的交互,除了模态的。我猜你和承诺这个设置将通过模态工作的人之间存在误解。无论如何,不​​要使用showmodal,只需禁用您要禁用的窗口即可。
  • @SertacAkyuz 模态窗口的重点不是禁用进程中的所有其他窗口,it's to disable the window's owner
  • ...该文档引用了previous document,其中包括作者自己的定义:“从最终用户的角度来看,当用户被锁定完成任务时,就会出现模态一旦开始,唯一的逃避就是取消整个操作。”

标签: delphi delphi-xe6


【解决方案1】:

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

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

  1. 禁用无模式所有者表单。
  2. 调用Show显示“模态”表单。
  3. 当“模态”表单关闭时,启用无模态所有者。确保在销毁“模态”表单的窗口之前启用所有者,如下所述。

您可以在第 2 步和第 3 步之间运行自己的模式消息循环,就像 ShowModal 所做的那样,但这可能有点过头了。我只是将表单显示为无模式,但禁用其所有者以使其相对于该所有者成为“模态”。

这个过程有点微妙。向ShowModal 的来源寻求灵感。此外,雷蒙德关于情态的史诗系列文章是必读的。我在这里链接到它:Why does a MessageBox not block the Application on a synchronized thread?

更多来自 Raymond:The correct order for disabling and enabling windows:

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

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

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

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

这一次,当模态对话框被销毁时,窗口管理器会查看所有者,嘿,这一次它是启用的,所以它继承了激活。

没有闪烁。没有闯入者。

【讨论】:

  • 雷蒙德的旧文;尤其是当我向 Sertac 评论什么是模态 UI 时,要小心禁用和启用哪个顺序的问题。我想从根本上说,Delphi 并没有使用 Windows 的模态对话框概念。我真的不想自己实现所有这些;我只是假设随着伟大的 Delphi 2009 过渡,所有这些错误都得到了解决。 :(
  • 编写自己的 ShowModal 是我面临的任务;但我有足够的经验知道我会弄错的。特别是当您收到WM_QUIT 时需要特殊情况。其他人在Delphi中解决了这个问题吗?打电话给@ZoëPeterson!
  • 评论 DisableTaskWindows/EnableTaskWindows 并希望最好?
  • 编写一个类助手。传入要禁用的打开的窗口数组。使用与 ShowModal 相同的代码。将 DisableTaskWindows/EnableTaskWindows 替换为禁用/启用提供的窗口的代码。工作完成。
  • 查看 DTW/ETW 的工作原理,并用提供的窗口枚举替换它们的枚举。
猜你喜欢
  • 1970-01-01
  • 2020-10-28
  • 2015-08-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-09
相关资源
最近更新 更多