Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to show a modal dialog from a modeless form?

I have two "modeless" forms:

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

enter image description here

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:

enter image description here

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.

like image 965
Ian Boyd Avatar asked Jul 29 '15 16:07

Ian Boyd


People also ask

What is modal modeless dialog box?

Modal dialog boxes, which require the user to respond before continuing the program. Modeless dialog boxes, which stay on the screen and are available for use at any time but permit other user activities.

How do you make a modeless dialog box?

To create a modeless dialog box, call your public constructor and then call the dialog object's Create member function to load the dialog resource. You can call Create either during or after the constructor call. If the dialog resource has the property WS_VISIBLE, the dialog box appears immediately.

What is Form how modal and modeless forms are implemented explain?

Main form creates a Modal dialog box and Modeless creates a dialog box when a user clicks on buttons. There are two classes, Modal and Modeless for each dialog box. Both are derived from the Form class. Implementations of classes are the same.

What is the example of modeless dialog box?

Here is an example: The Find (and the Replace) dialog box of WordPad (also the Find and the Replace dialog boxes of most applications) is an example of a modeless dialog box. If it is opened, the user does not have to close it in order to use the application or the document in the background.


1 Answers

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.

like image 175
David Heffernan Avatar answered Oct 13 '22 18:10

David Heffernan