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:
- Disable the modeless owner form.
- Show the "modal" form by calling
Show
. - 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.
来源:https://stackoverflow.com/questions/31705874/how-to-show-a-modal-dialog-from-a-modeless-form