9

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.

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • 1
    When the modal window is shown, does the framework disable the main form? That's what you need to stop happening. – David Heffernan Jul 29 '15 at 16:27
  • 1
    I guess you start by looking at how DisableTaskWindows operates – David Heffernan Jul 29 '15 at 16:43
  • 5
    That's what modal means, it disables interaction with all the forms of the same thread but the modal one. I guess there was a misunderstanding between you and whoever promised that this setup would work through modality. Anyway, don't use showmodal, just disable the window that you want to be disabled. – Sertac Akyuz Jul 29 '15 at 16:48
  • 1
    @SertacAkyuz The point of a modal window isn't to disable all other windows in the process, [it's to disable the window's owner](http://blogs.msdn.com/b/oldnewthing/archive/2011/01/21/10118482.aspx) – Ian Boyd Jul 29 '15 at 17:44
  • @DavidHeffernan Yes, calling ShowModal also disables the main form. – Ian Boyd Jul 29 '15 at 18:18
  • 1
    @IanBoyd: In fact, `ShowModal()` disables **all** visible windows that belong to the calling thread. This is by design. It is just not the design you want. You need to use `Show()` instead of `ShowModal()` in this case, disabling only the `OwnerForm` when the confirm window is shown, and re-enabling the `OwnerForm` when the confirm window is closed. – Remy Lebeau Jul 29 '15 at 18:53
  • 1
    @RemyLebeau I realize `ShowModal` disables all windows in the application. But as a Windows developer, creating a Windows application, i am trying to show a window modally. On Windows *[A modal window is an owned window that requires the user to interact with it before they can return to operating the owner window.](http://blogs.msdn.com/b/oldnewthing/archive/2011/12/12/10246541.aspx)*. The built-in `ShowModal` of the VCL doesn't so that; so i presume i have to write it myself? – Ian Boyd Jul 29 '15 at 20:12
  • 1
    @IanBoyd: Yes, you have to write it yourself. – Remy Lebeau Jul 29 '15 at 20:21
  • 1
    @Ian - You're picking *one of the steps* that leads to the definition of a *modal window* in perspective of a customer who required to determine if a particular window is modal. If you continue to read on the document you linked, you'll find the author refers to this definition (the final definition, not the one you quoted) as *the right definition for the customer's needs*.... – Sertac Akyuz Jul 29 '15 at 22:16
  • 2
    ... The document refers to a [previous document](http://blogs.msdn.com/b/oldnewthing/archive/2005/02/18/376080.aspx) which includes author's own definition: *"From the end-users' point of view, modality occurs when the users are locked into completing a task once it is begun, with the only escape being to cancel the entire operation."* – Sertac Akyuz Jul 29 '15 at 22:16
  • @ProgrammerNotFound Yes, WoW! It was one of three icons that i found in **My Documents** for the demo app. Light be with you. – Ian Boyd Jul 30 '15 at 14:30
  • @Ian Regarding your edit, consider this scenario. Main form shows modeless window. Modeless window shows modal form. How many forms should be disabled? I wonder what happens when you call MessageBox in this scenario, passing the modeless window as the owner. – David Heffernan Jul 30 '15 at 16:42
  • 1
    *How many forms should be disabled?*: Just the owner hWnd. *What happens if i pass a disabled window as the owner of a modal dialog box?* I tested it by calling `MessageBox` once during a click, and the other 5 seconds later from a timer. You have two modal windows, both owned by the same owner, both interactiable. Only once the first modal dialog is dismissed does the owner become enabled. It seems Windows checks only enables the owner if it was the one to disable it. And it only disables it if it isn't already disabled. Which is a good note to add to my `.ShowDialog`. Thanks! – Ian Boyd Jul 30 '15 at 19:49
  • Here are some other correct definitions/examples for you: [WinForms](https://msdn.microsoft.com/en-us/library/aa984358%28v=vs.71%29.aspx): *A modal form or dialog box must be closed or hidden before you can continue working with the rest of the application.* [WPF](https://msdn.microsoft.com/en-us/library/ms599715%28v=vs.110%29.aspx): *ShowDialog shows the window, disables all other windows in the application, and returns only when the window is closed. This type of window is known as a modal window." ... – Sertac Akyuz Jul 31 '15 at 02:08
  • ... **Office**: Float navigation pane in Word, open a modal dialog. Main window and the pane is disabled. **Windows**: Start wordpad, launch find dialog. Open any modal dialog. Main window and find dialog are disabled. Rest of your items are quite subjective and doesn't match my experience/expectations. It should be apparent by now that the api does not specify modal behavior. It specifies what a modal dialog is, which you come up with now and again. But if that specification is all you have, then the VCL have got it right. It is just mean.... – Sertac Akyuz Jul 31 '15 at 02:09
  • I wish you stopped trying to impose your **correct** modal behavior through your question which is completely unrelated to the technical matter. Nobody said that you shouldn't have an enabled window when there is a modal one. Just start considering that this can be not what everybody expects. In my opinion, majority... But evidently this is debatable. – Sertac Akyuz Jul 31 '15 at 02:16
  • @David - With the default MB_APPLMODAL, main window remains enabled. If you use MB_TASKMODAL while passing 0 for hwnd, all thread windows are disabled. – Sertac Akyuz Jul 31 '15 at 02:20
  • @Ian Surely you can see that different scenarios call for different designs. There is no single standard here. – David Heffernan Jul 31 '15 at 05:30
  • 1
    It's standard enough throughout Windows. WinForms, WPF, TaskDialog, ProgressDialog, SHFileOperation, IFileOperation. Lets call this preferred behavior [*"the pit of success"*](http://blog.codinghorror.com/falling-into-the-pit-of-success/), and Delphi's inability to do it *"the pit of failure"*. I would argue there is one standard here. But even we did allow that the 99.9% case means 0.01% of the time i couldn't do it - Delphi can't do the 99.9% case. – Ian Boyd Jul 31 '15 at 15:58
  • Quite astounding how you deny statements from the developer teams of the products and examples from the programs and systems that you exemplify. I don't really care my causing a fair question becoming an unnecessary wall of false assertions as I trust programmers, in general, are an intelligent kind. – Sertac Akyuz Jul 31 '15 at 21:04
  • 1
    @SertacAkyuz Those example are *owned* modeless dialogs. Which we are, obviously, not talking about (as we're talking about unowned modeless dialogs). Of course owned windows are disabled when the owner is disabled. My problem is that *unowned* windows are disabled when an unrelated unowned window shows a dialog. – Ian Boyd Aug 02 '15 at 19:07

1 Answers1

7

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.

Community
  • 1
  • 1
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 2
    Raymond's old article; and especially the issue of being careful which order to disable and enable in, was going through my mind when i commented to Sertac what a modal UI is. I guess fundamentally Delphi doesn't do the Windows concept of a modal dialog. I'd really hate to have to implement all of this myself; i just assumed that with the great Delphi 2009 transition all of these bugs were solved. :( – Ian Boyd Jul 29 '15 at 18:51
  • @Ian I don't think there's one single standard here. It's plausible that one app would want to disable just the modeless window, but another would need to disable both modeless and main window. That's a choice to be made by the app. The VCL chooses the latter for us but it can't read our minds. A more flexible design would allow us to choose which windows to disable. It would be easy enough to write a better ShowModal that allowed you to pass in windows that should not be disabled. You could readily do that I think. – David Heffernan Jul 29 '15 at 18:57
  • 1
    Writing my own ShowModal is the task i'm faced with; but i'm experienced enough to know i'll get it wrong. Expecially when it comes to things like the special case needed if you receive a `WM_QUIT`. Has anyone else solved this in Delphi? Calling @ZoëPeterson! – Ian Boyd Jul 29 '15 at 19:00
  • 1
    Comment out `DisableTaskWindows`/`EnableTaskWindows` and hope for the best? – Ian Boyd Jul 29 '15 at 19:09
  • 2
    Write a class helper. Pass in an open array of windows to be disabled. Use the same code as ShowModal. Replace DisableTaskWindows/EnableTaskWindows with code to disable/enable the supplied windows. Job done. – David Heffernan Jul 29 '15 at 19:13
  • 1
    Look at how DTW/ETW works and replace their enumeration with enumeration of the supplied windows. – David Heffernan Jul 29 '15 at 19:14
  • 1
    I was going to model it after Windows/WinForms: `.ShowModal(OwnerWnd: HWND)`. Then just be sure to fiddle the disabling and enabling correctly. – Ian Boyd Jul 29 '15 at 19:15