如何从无模式形式显示模式对话框? [英] How to show a modal dialog from a modeless form?

查看:99
本文介绍了如何从无模式形式显示模式对话框?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两种无模式 形式:




  • 一种是特殊的 MainForm

  • 另一个是无模式形式





您可以看到:




  • 两者都存在于任务栏上

  • 都有任务栏按钮

  • 都可以独立最小化

  • 都可以独立恢复

  • 两个都不总是在另一个(拥有)上



现在显示模态形式



从这种无模态形式,我想显示一个模态形式:




对话框有两种基本类型:




  • 模式对话框要求用户先完成并关闭,然后才能继续使用所有者窗口。这些对话框最适合用于需要完成才能完成的关键或不频繁的一次性任务。

  • 无模式对话框允许用户在对话框之间进行切换和所有者窗口(根据需要)。这些对话框最适合用于频繁,重复,正在进行的任务。


Windows具有所有者 的概念。 拥有 窗口将始终显示在其所有者的顶部。当窗口为模态 时,表示禁用所有者,直到模态任务完成。



您会在 ProgressDialog API:


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

hwndParent [in]

类型: HWND

对话框父窗口的句柄。



dwFlags

类型: DWORD

PROGDLG_MODAL

进度对话框将显示为 hwndParent 指定的窗口。默认情况下,进度对话框是无模式的。


当然可以,并禁用所有其他窗口




  • 在线程中

  • 进程

  • 或系统



但是我想具有正确行为。我想做的事:




  • Windows做什么

  • Office应用程序做什么

  • 超越比较的功能

  • WinForms的功能

  • WPF的功能

  • 的功能我曾经使用过的每个应用程序都会

  • 以及任何用户会期望的



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

解决方案

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



您必须精巧显示此表单,以使其表现出所需的效果。请执行以下操作:


  1. 禁用无模式所有者表单。

  2. 通过调用 Show c显示模式表单。

  3. 模式出现时表单关闭后,启用无模式所有者。如下所述,请确保在销毁模式表单窗口之前启用了所有者。

您可能会在第2步和第3步之间运行自己的模式消息循环,如 ShowModal 确实可以,但是这可能会过大。我只是将表单显示为无模式,但禁用了它的所有者以使其相对于该所有者为模式。



此过程有些微妙。从 ShowModal 的源中获取灵感。此外,雷蒙德(Raymond)关于情态的史诗系列文章也是必不可少的阅读内容。我在这里链接到所有内容:为什么MessageBox不会在同步线程上阻止应用程序?



甚至还有来自Raymond的更多信息:禁用和启用Windows的正确顺序


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



这就是为什么您得到怪异的闯入者窗口的原因。



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




  • 重新启用所有者。

  • 销毁模式对话框。



这一次,当模式对话框被销毁时,窗口管理器会向所有者看,并且这次启用了它,所以它继承了激活。



没有闪烁。没有闯入者。



I have two "modeless" forms:

  • one is the special MainForm
  • the other is a modeless form

You can see:

  • both exist on the taskbar
  • both have a taskbar button
  • both can be independantly minimized
  • both can be independantly restored
  • neither is always on top (owned) by the other

Now show a modal form

From this modeless form, i want to show a modal one:

The Modal form is being constructed as:

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;

The modal form is told which owner to use through a new OwnerForm property:

protected
   procedure SetOwnerForm(const Value: TForm);
public
   property OwnerForm: TForm read GetOwnerForm write SetOwnerForm;
end;

which forces an handle recreation:

procedure TfrmExchangeConfirm.SetOwnerForm(const Value: TForm);
begin
    FOwnerForm := Value;

    if Self.HandleAllocated then
        Self.RecreateWnd;
end;

and is then the second time through CreateParams:

procedure TfrmExchangeConfirm.CreateParams(var Params: TCreateParams);
begin
    inherited;

    if FOwnerForm <> nil then
        Params.WndParent := FOwnerForm.Handle;
end;

The problem is:

  • once this owned modal form is shown, i cannot interact with the MainForm
  • i cannot minimize the MainForm using the taskbar button
  • i cannot minimize the Modal, or its owning parent, using the taskbar button
  • if i minimize the modal form using the Minimize button, the MainForm disappears
  • i can activate the MainForm using its taskbar button; but i cannot interact with it

I've asked this question about 7 times over the last decade. The last time i was promised that making the main form the MainForm would solve everything.

Bonus: WinForms has handled this correctly since .NET 1.0.

There is a lot of confusion about what a modal dialog is. A dialog is modal when you must interact with it before you can continue to use its owner. From the 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 has the concept of an "owner". When a window is "owned" that will will always appear on top of its owner. When a window is "modal", it means that the owner is disabled until the modal task is complete.

You an see this effect in the 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.

Sure, you could be mean, and disable all other windows

  • in the thread
  • the process
  • or the system

But i want to have the correct behavior. I want to do:

  • what Windows does
  • what Office applications do
  • what Beyond Compare does
  • what WinForms does
  • what WPF does
  • what every application i've ever used does
  • and what any user would expect

I've wanted this in my Delphi apps since 1998; when realized Delphi 3 didn't properly support Windows 95 and the taskbar.

解决方案

ShowModal disables all other top level windows in the same thread. That includes your main form.

You'll have to finesse the showing of this form to make it behave the way you want. Do the following:

  1. Disable the modeless owner form.
  2. Show the "modal" form by calling Show.
  3. When the "modal" form is closed, enable the modeless owner. Make sure the owner is enabled before the "modal" form's window is destroyed, as explained below.

You could potentially run your own modal message loop in between steps 2 and 3, as ShowModal does but this might be overkill. I'd just show the form modeless but disable its owner to make it "modal" with respect to that owner.

This process is a little delicate. Look to the source of ShowModal for inspiration. Also, Raymond's epic series of articles on modality is essential reading. I link to it all here: Why does a MessageBox not block the Application on a synchronized thread?

And even more from 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.

这篇关于如何从无模式形式显示模式对话框?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆