It's not too hard to do this, though it starts getting complicated quickly depending on how complete you want it to be. Getting multiple modal dialogs to work independently is a ton of effort.
To start, you need to avoid Application.MainForm entirely. Always use Form := TMyForm.Create(Application)
instead of Application.CreateForm(TMyForm, Form)
. The later sets MainForm and you never want that to happen.
To make things shut down properly you'll need to do something like this in your form's OnClose
event handler:
if Screen.FormCount = 1 then
Application.Terminate;
CloseAction := caFree;
Application.Run
relies on MainForm being assigned, so in your DPR replace that line with this loop:
repeat
try
Application.HandleMessage;
except
Application.HandleException(Application);
end;
until Application.Terminated;
There are a couple of ways to handle the taskbar entry.
Single taskbar entry: Set Application.MainFormOnTaskbar := False;
and the hidden TApplication handle will be used. Clicking on the taskbar entry will bring all of the windows to the front. You'll need to override Application.OnMessage
or add a TApplicationEvents
component, and watch for WM_CLOSE
with the Msg.Handle = Application.Handle`. In that case the user has right-clicked on the taskbar and selected Close, so you should close all the windows.
Multiple taskbar entries: Set Application.MainFormOntaskbar := True
. Override your form's CreateParams
method and set Params.WndParent := 0;
. Each taskbar entry will control that form.
There are probably a few other gotchas, but that's the basics.
As I said, making ShowModal
and TOpenDialog/TSaveDialog
working independently, so it only affects its parent form and so multiple dialogs can be open at once, is a ton of work, and I can't really recommend it. If you're a masochist, here's the general steps:
Replace TCustomForm.ShowModal
with a custom version. Among other things, that routine disables all the other windows in the application, so you need to replace the DisableTaskWindows/EnableTaskWindows
calls with EnableWindow(Owner.Handle, False/True)
to just disable the parent form. At this point you can open multiple dialogs, but they can only be closed in last-in, first-out order, because the calls end up being recursive. If that's fine, stop here.
There are two ways to work around that:
Rather than making ShowModal
blocking, have StartModal
and EndModal
routines that have the first bit and last bit of ShowModal's code and call an OnShowModalDone
event when the dialog is closed. This is kind of a pain to use, but is relatively easy to code and easy to make stable.
Use the Windows fiber routines to swap out the stack and start a new message loop. This approach is easy to use, because ShowModal
is blocking, so you call it like normal. This is the approach we used in Beyond Compare. Don't do it. It's complicated to write, there will be stability issues for non-trivial applications because of incompatibilities with third party code (Windows global message hooks, TWebBrowser, .NET in shell extensions loaded by the browse dialog, etc), and if it's a cross-platform project, the Unix ucontext functions aren't safe to use either.
The common dialogs (TOpenDialog, TColorDialog, etc), have similar restrictions. To make them only disable the parent form you need to override TCommonDialog.TaskModalDialog
and replace the DisableTaskWindows/EnableTaskWindows
calls there too. They can't be made asynchronous like the regular Delphi dialogs above though, since they're blocking functions provided by Windows (GetOpenFileName, ChooseColor, etc). The only way to allow those to close in any order is to have each dialog run in a dedicated thread. Windows can handle most of the synchronization to do that, as long as you're careful about accessing the VCL objects, but it basically involves rewriting large portions of Dialogs.pas
.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…