SDI application with multiple instances shown on taskbar - delphi

I've created an SDI application using the Delphi Berlin VCL template. I can create additional instances by programming File|New as follows:
procedure TSDIAppForm.FileNew1Execute(Sender: TObject);
var
LNewDoc: TSDIAppForm;
begin
LNewDoc := TSDIAppForm.Create(Application);
LNewDoc.Show;
end;
Only the owner form shows on the taskbar. Also, closing the owner form closes all the instances. How do I unlink the additional instances so that they operate independently and show individually on the taskbar?

Closing the TForm that is assigned as the Application.MainForm exits the app, that is by design.
If you want the MainForm to act like any other SDI window and be closed independently without exiting the app if other SDI windows are still open, you will have to create a separate TForm to act as the real MainForm and then hide it from the user (set Application.ShowMainForm to false at startup before Application.Run() is called), and then you can create TSDIAppForm objects as needed. When the last TSDIAppForm object is closed, you can then close the MainForm, or call Application.Terminate() directly, to exit the app.
To give each TSDIAppForm its own Taskbar button, you need to override the virtual CreateParams() method:
How can I get taskbar buttons for forms that aren't the main form?
Try this:
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TMyRealMainForm, MyRealMainForm);
Application.CreateForm(TSDIAppForm, SDIAppForm);
SDIAppForm.Visible := True;
Application.ShowMainForm := False;
Application.Run;
end.
procedure TSDIAppForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
Params.WndParent := 0;
end;
procedure TSDIAppForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
procedure TSDIAppForm.FormDestroy(Sender: TObject);
begin
if Screen.FormCount = 2 then // only this Form and the MainForm
Application.Terminate;
end;
procedure TSDIAppForm.FileNew1Execute(Sender: TObject);
var
LNewDoc: TSDIAppForm;
begin
LNewDoc := TSDIAppForm.Create(Application);
LNewDoc.Show;
end;

Related

Handling NIN_POPUPOPEN, NIN_POPUPCLOSE message of system tray icon

I want to display a form when the cursor enters the icon, and it disappears shortly after the cursor leaves the icon, similar to the Process Hacker software.
(It displays a form above the system tray that displays information about running applications when the cursor enters the icon, with additional options such as attaching the form that will not disappear, opening settings and more, and the form disappears a few seconds after leaving the icon)
I saw this post
Edit:
I do not need to add these events as properties (like the built-in OnMouseMove, and as in TTrayIconEx), I just want to add a handler procedure that will receive the messages sent from the icon when the cursor hovers over it or leaves it. For example, the messages Remy Lebeau mentioned in his reply (NIN_POPUPOPEN, NIN_POPUPCLOSE)
Is this possible, and how?
updating:
In fact, I used this code as experience:
unit MainFormtest;
interface
uses
ShellAPI, Windows, Messages, SysUtils, Classes,
Vcl.Controls, Vcl.Forms, Vcl.ExtCtrls, Vcl.Menus;
type
TTesterForm = class(TForm)
DelayHide: TTimer;
TestPopupMenu: TPopupMenu;
PMClose: TMenuItem;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure DelayHideTimer(Sender: TObject);
procedure FormMouseEnter(Sender: TObject);
procedure FormMouseLeave(Sender: TObject);
procedure FormHide(Sender: TObject);
procedure PMCloseClick(Sender: TObject);
procedure TestPopupMenuPopup(Sender: TObject);
protected
procedure CreateParams(var Params: TCreateParams); override;
public
TrayIconForTest: TNotifyIconData;
procedure TrayMouseMessage(var Msg: TMessage); Message WM_SYSTEM_TRAY_MESSAGE;
end;
var
TesterForm: TTesterForm;
implementation
{$R *.dfm}
procedure TTesterForm.FormCreate(Sender: TObject);
begin
with TrayIconForTest do
begin
cbSize := TNotifyIconData.SizeOf;
Wnd := Handle;
uID := $20;
uFlags := NIF_MESSAGE or NIF_ICON or NIF_TIP;
uCallBackMessage := WM_SYSTEM_TRAY_MESSAGE;
hIcon := Application.Icon.Handle;
szTip := 'this is test icon';
uVersion := 4;
dwInfoFlags := NIIF_USER;
hBalloonIcon := Application.Icon.Handle;
end;
Shell_NotifyICon(NIM_ADD, #TrayIconForTest);
Shell_NotifyIcon(NIM_SETVERSION, #TrayIconForTest);
end;
procedure TTesterForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.ExStyle := Params.ExStyle and not WS_EX_APPWINDOW;
Params.WndParent := Application.Handle;
end;
procedure TTesterForm.TestPopupMenuPopup(Sender: TObject);
begin
DelayHide.Enabled := False;
end;
procedure TTesterForm.TrayMouseMessage(var Msg: TMessage);
begin
case LOWORD(Msg.Lparam) of
NIN_POPUPOPEN:
begin
TesterForm.Show;
DelayHide.Enabled := False;
TestPopupMenu.CloseMenu;
end;
NIN_POPUPCLOSE:
DelayHide.Enabled := True;
WM_RBUTTONDOWN, WM_LBUTTONDOWN:
TestPopupMenu.Popup(Mouse.CursorPos.X, Mouse.CursorPos.Y);
end;
end;
procedure TTesterForm.DelayHideTimer(Sender: TObject);
begin
TesterForm.Hide;
end;
procedure TTesterForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Shell_NotifyIcon(NIM_DELETE, #TrayIconForTest);
Application.Terminate;
end;
procedure TTesterForm.FormHide(Sender: TObject);
begin
DelayHide.Enabled := False;
end;
procedure TTesterForm.FormMouseEnter(Sender: TObject);
begin
DelayHide.Enabled := False;
end;
procedure TTesterForm.FormMouseLeave(Sender: TObject);
begin
DelayHide.Enabled := True;
end;
procedure TTesterForm.PMCloseClick(Sender: TObject);
begin
testerForm.Close;
end;
end.
But he suffers from several problems:
When I press the right / left button to open the menu, the message NIN_POPUPCLOSE is not read again until the cursor enters the icon again and exits.
The menu does not close when a mouse button is pressed anywhere (so I added TestPopupMenu.CloseMenu to close the menu when the cursor re-enters).
The form window tends to display in the back when targeting another application window. I tried applying TesterForm.BringToFront and also SetForegroundWindow and it did not help.
Can you direct me to fix the code?
TTrayIcon, and TTrayIconEx provided in the other post you mention, do not support what you want.
However, the underlying Win32 tray icon API, Shell_NotifyIcon(), does. After adding your icon with NIM_ADD, you have to use NIM_SETVERSION setting NOTIFYICONDATA.uVersion to NOTIFYICON_VERSION_4 or higher to enable the API to send NIN_POPUPOPEN and NIN_POPUPCLOSE notification messages to your tray icon's owner window (note: this works only on Vista+):
NIN_POPUPOPEN. Sent when the user hovers the cursor over an icon to indicate that the richer pop-up UI should be used in place of a standard textual tooltip.
NIN_POPUPCLOSE. Sent when a cursor no longer hovers over an icon to indicate that the rich pop-up UI should be closed.
UPDATE:
The menu does not close when a mouse button is pressed anywhere (so I added TestPopupMenu.CloseMenu to close the menu when the cursor re-enters).
This is a well-known issue (multiple questions on StackOverflow about it), and is covered in the Remarks section of the documentation for the Win32 TrackPopupMenu() function (which TPopupMenu.Popup() calls internally):
To display a context menu for a notification icon, the current window must be the foreground window before the application calls TrackPopupMenu or TrackPopupMenuEx. Otherwise, the menu will not disappear when the user clicks outside of the menu or the window that created the menu (if it is visible). If the current window is a child window, you must set the (top-level) parent window as the foreground window.
However, when the current window is the foreground window, the second time this menu is displayed, it appears and then immediately disappears. To correct this, you must force a task switch to the application that called TrackPopupMenu. This is done by posting a benign message to the window or thread, as shown in the following code sample:
SetForegroundWindow(hDlg);
// Display the menu
TrackPopupMenu( hSubMenu,
TPM_RIGHTBUTTON,
pt.x,
pt.y,
0,
hDlg,
NULL);
PostMessage(hDlg, WM_NULL, 0, 0);
In this case, hDlg can be the Handle of your TForm, eg:
WM_RBUTTONDOWN, WM_LBUTTONDOWN: begin
SetForegroundWindow(Handle);
with Mouse.CursorPos do
TestPopupMenu.Popup(X, Y);
PostMessage(Handle, WM_NULL, 0, 0);
end;

Closing modal form(s) from outside and opening a new modal form

In my main form I have a button that opens a modal Form2 (which may open other modal forms). before opening Form2 I'm setting a timer, that will programmatically close all active modal forms (Form2.Close) and open a new modal Form3.
problem is that when Form3 is opening modally, Form2 remains (visible) and only when I close Form3 by clicking the X will Form2 close.
To reproduce add 3 forms to a project add a TButton, and drop a TTimer on Form1 (main form):
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Timer1: TTimer;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
public
end;
var
Form1: TForm1;
implementation
uses Unit2, Unit3;
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
Timer1.Enabled := False;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Timer1.Enabled := True;
with TForm2.Create(Application) do
try
ShowModal;
finally
Free;
end;
end;
procedure CloseActiveModalForms;
var
I: Integer;
F: TCustomForm;
L: TList; // list of modal forms
begin
L := TList.Create;
try
for I := 0 to Screen.CustomFormCount - 1 do
begin
F := Screen.CustomForms[I];
if (fsModal in F.FormState) then
L.Add(F);
end;
for I := 0 to L.Count - 1 do
TCustomForm(L.Items[I]).Close; // this sets ModalResult := mrCancel
finally
L.Free;
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
CloseActiveModalForms; // this should close TForm2 but it does not.
with TForm3.Create(Application) do // create new Modal TForm3
try
ShowModal;
finally
Free;
end;
end;
end.
why Form2 is not closing? why is the Form2 modal loop not exiting after I call CloseActiveModalForms?
Your call stack looks as follows:
1 Form1.Button1Click
2 Form2.ShowModal //Local message processing loop until form closes
3 Form1.Timer1Timer //Here you attempt to close the form
//but it doesn't actually until ShowModal exits
4 Form3.ShowModal // Another message loop that doesn't return until form closes
So basically, you're not able to finish the closing of Form2 until after Form3 has closed. Note that ShowModal is a blocking call to show a form. If you just Show Form3 (i.e. not ShowModal) the call doesn't block, and you'll see Form2 is able to close as the call-stack unwinds.
You should be able to work around this by delaying your call to show Form3 until after Form2 has closed. The OnFormDestroyEvent should suffice (unfortunately I can't test it).
procedure TForm1.ShowForm3(Sender: TObject);
var
LForm: TForm;
begin
LForm := TForm3.Create(Application); //as you created it, but nil owner should suffice
try
LForm.ShowModal;
finally
LForm.Free;
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
//You will need to figure out how you reference the Form2 instance.
Form2.OnFormDestroy := ShowForm3;
CloseActiveModalForms;
//Form2 will close after you backtrack up the call-stack.
//When it's destroyed, your event handler will create and show a TForm3 instance.
end;
Note the above simply demonstrates the concept. You would need to devise a more robust approach depending on whatever your ultimate objective may be.
However, I would advise that excessive use of modal forms is generally considered unfriendly in terms of user experience.

Delphi XE4 - Form create dynamically in DLL results in AV

I have a DLL application that is loaded in my main application.
The DLL contains a form that is created at runtime.
The functionality is:
In main application I have a menu which whenever pressed calls a procedure from within the DLL. This procedure dynamically creates the form.
procedure doCreateForm;
var
myForm: TForm1;
begin
myForm := TForm1.Create(nil)
try
...
except
myForm.Free;
end;
end;
The closing procedures:
procedure CloseWindow(ASender: TForm1);
begin
FreeAndNil(ASender);
Application.ProcessMessages;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
CloseWindow(Self);
end;
The problem (access violation) occurs only on the second attempt to create the form. Not first time, not 3rd, 4th, 5th and so on.
So I click on the menu, the form is created (dynamically) and closed (if condition is not satisfied during form create event). I click again on the menu, and when myForm.Create(nil) is called, AV raises. I click again on the menu, and all is ok. I click again and again and again and all is ok. Only when pressed the 2nd time, AV raises.
Is there something wrong with dynamic creation of visual form in DLL?
A more detailed explanation:
The chain is:
I create MyForm (myForm := TForm1.Create(nil))
Prior to showing the form I do some conditioning tests.
If all is ok, myForm.Show - this is working fine and I can also close myForm properly
If something is wrong:
a). I create a message form myMessageForm := TMyMessageForm.Create(nil) that contains a closing timer (the form closes after 10s). This form has action:=caFree in onClose event
b). I call myForm.Close. This form has also action:=caFree in onClose event - this form closes before myMessageForm closes (due to the timer present in myMessageForm)
Both forms are created with nil owner, but they are connected in some way (I don't know why). and the destruction of forms is not performed correctly. The next time myForm.Create(nil) or myMessageForm.Create(nil) is called, access violation occurs.
The myMessageForm should be created independently from myForm and it's destruction should not condition myForm destruction in any way.
unit1;
procedure doCreateForm;
var
myForm: TForm1;
begin
myForm := TForm1.Create(nil)
try
with myForm do
begin
if <test condition true> then Show
else
begin
ShowErrMessage('Error', 'Error message text', errType);
Close;
end;
end;
except
myForm.Free;
end;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
unit2;
procedure ShowErrMessage(title, text: string; err: mtErrType);
var
myMessageForm: TMyMessageForm;
begin
myMessageForm := TMyMessageForm.Create(nil)
try
with myMessageForm do
begin
StepDownCounter := 10;
CloseTimer.Enable := True;
end;
except
myMessageForm.Free;
end;
end;
procedure TMyMessageForm.CloseTimerTimer(Sender: TObject);
begin
StepDownCounter := StepDownCounter - 1;
if (StepDownCounter < 1) then
begin
CloseTimer.Enabled := False;
LabelStepDownText.Visible := False;
Close;
end
else
begin
LabelStepDownText.Caption := 'Window will close in ' + IntToStr(StepDownCounter) + 's';
LabelStepDownText.Visible := True;
end;
end;
procedure TMyMessageForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
You are destroying the object that is executing the current method. All code in that object that is executed after the object is destroyed is invalid.
You should set the close action to caFree, or use Release, as has been explained. These work by posting a message to the queue to allow the object to be destroyed after the current method returns. You are subverting that with the call to ProcessMessages which pumps the queue. Remove the call to ProcessMessages.

How to not have a MainForm in Delphi?

i've been trying to get some modeless forms in my application to appear on the taskbar - taking advantage of the new useful taskbar in Windows 7.
There's are many issues with the VCL that need to be undone before a form can exist on the taskbar.
But the final issue is that minimizing the form that the VCL has designated the main form causes all windows in the application to vanish.
Ten years ago, Peter Below (TeamB) documented these problems, and attempts to work around them. But there are some issues that cannot be solved. The issues run so deep within the VCL itself, that it's effectively impossible to make Delphi applications behave properly.
It all stems from the fact that the button you see on the toolbar does not represent the application's window; it represents the TApplications window, which is hidden and never seen. And then there is the application's MainForm, which is then imbued with special abilities where if it is minimized then it instructs the application to hide itself.
It seems to me that if i can do
Application.MainForm := nil;
then all these bugs would go away. The application can have its hidden window, and in the meantime i'll override every other form in the application, including my main form, with:
procedure TForm2.CreateParams(var params: TCreateParams );
begin
inherited CreateParams(params);
params.ExStyle := params.ExStyle or WS_EX_APPWINDOW;
end;
But in Delphi the Application.MainForm property is read-only.
How can i not have a MainForm in Delphi?
See also
(stackoverflow) Delphi: What is Application.Handle?
(newsgroup) Hiding Main Window but not child
You cannot run a GUI project without a MainForm assigned. The main message loop will exit immediately without one. However, that does not mean that the MainForm has to run your UI. You can use a blank hidden TForm as the assigned MainForm, and then have it instantiate your real MainForm as a secondary TForm. For example:
HiddenMainFormApp.dpr:
project HiddenMainFormApp;
uses
..., Forms, HiddenMainForm;
begin
Application.Initialize;
Application.CreateForm(THiddenMainForm, MainForm);
Application.ShowMainForm := False;
Application.Run;
end.
HiddenMainForm.cpp:
uses
..., RealMainForm;
procedure THiddenMainForm.FormCreate(Sender: TObject);
begin
RealMainForm := TRealMainForm.Create(Self);
RealMainForm.Show;
end;
RealMainForm.cpp:
procedure TRealMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
Application.Terminate;
end;
Alternatively:
HiddenMainFormApp.dpr:
project HiddenMainFormApp;
uses
..., Forms, HiddenMainForm, RealMainForm;
begin
Application.Initialize;
Application.CreateForm(THiddenMainForm, MainForm);
Application.ShowMainForm := False;
RealMainForm := TRealMainForm.Create(Application);
RealMainForm.Show;
RealMainForm.Update;
Application.Run;
end.
RealMainForm.cpp:
procedure TRealMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
Application.Terminate;
end;
You can't, especially in Delphi 5.
Your quote concerning the TApplication window being the one seen on the task bar hasn't been true for several Delphi versions now (I believe D2007 changed it).
Because you're using Delphi 5, you're using an outdated copy of Delphi; current versions have almost none of the things you're writing about any longer. I'd suggest you upgrade to a later version of Delphi (D5 is extremely old); Delphi 2007 if you need to avoid Unicode, Delphi XE if you can use (or don't mind having) Unicode support in the VCL and RTL.
The things you're describing are not bugs, BTW. They were intentional design decisions made at the time Delphi 1 was being designed, and through Delphi 7 worked fine with the versions of Windows that were available. Changes in later versions of Windows (XP/Vista/Win7 and the equivalent Server versions) made changes in that architecture necessary, and they were made as Delphi progressed along with Windows. Because you've chosen not to progress with your version of Delphi to keep it more recent doesn't make the things you write about magically become bugs. :-)
Having Application.MainForm assigned seems not to be a problem here for showing another modeless form on the taskbar while minimizing the MainForm.
Project1.dpr:
program Project1;
uses
Forms,
Windows,
Unit1 in 'Unit1.pas' {MainForm},
Unit2 in 'Unit2.pas' {Form2};
{$R *.res}
var
MainForm: TMainForm;
begin
Application.Initialize;
Application.CreateForm(TMainForm, MainForm);
ShowWindow(Application.Handle, SW_HIDE);
Application.Run;
end.
Unit1.pas:
unit Unit1;
interface
uses
Windows, Messages, Classes, Controls, Forms, StdCtrls, Unit2;
type
TMainForm = class(TForm)
ShowForm2Button: TButton;
ShowForm2ModalButton: TButton;
procedure ShowForm2ButtonClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure ShowForm2ModalButtonClick(Sender: TObject);
private
FForm2: TForm2;
procedure ApplicationActivate(Sender: TObject);
procedure Form2Close(Sender: TObject; var Action: TCloseAction);
procedure WMSysCommand(var Msg: TWMSysCommand); message WM_SYSCOMMAND;
protected
procedure CreateParams(var Params: TCreateParams); override;
end;
implementation
{$R *.dfm}
procedure TMainForm.FormCreate(Sender: TObject);
begin
Visible := True; //Required only for MainForm, can be set designtime
Application.OnActivate := ApplicationActivate;
end;
procedure TMainForm.ApplicationActivate(Sender: TObject);
{ Necessary in case of any modal windows dialog or modal Form active }
var
TopWindow: HWND;
I: Integer;
begin
TopWindow := 0;
for I := 0 to Screen.FormCount - 1 do
begin
Screen.Forms[I].BringToFront;
if fsModal in Screen.Forms[I].FormState then
TopWindow := Screen.Forms[I].Handle;
end;
Application.RestoreTopMosts;
if TopWindow = 0 then
Application.BringToFront
else
SetForegroundWindow(TopWindow);
end;
procedure TMainForm.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
with Params do
begin
ExStyle := ExStyle or WS_EX_APPWINDOW;
WndParent := GetDesktopWindow;
end;
end;
procedure TMainForm.WMSysCommand(var Msg: TWMSysCommand);
begin
if Msg.CmdType = SC_MINIMIZE then
ShowWindow(Handle, SW_MINIMIZE)
else
inherited;
end;
{ Testing code from here }
procedure TMainForm.ShowForm2ButtonClick(Sender: TObject);
begin
if FForm2 = nil then
begin
FForm2 := TForm2.Create(Application); //Or: AOwner = nil, or Self
FForm2.OnClose := Form2Close;
end;
ShowWindow(FForm2.Handle, SW_RESTORE);
FForm2.BringToFront;
end;
procedure TMainForm.Form2Close(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
FForm2 := nil;
end;
procedure TMainForm.ShowForm2ModalButtonClick(Sender: TObject);
begin
with TForm2.Create(nil) do
try
ShowModal;
finally
Free;
end;
end;
end.
Unit2.pas:
unit Unit2;
interface
uses
Windows, Messages, Classes, Controls, Forms;
type
TForm2 = class(TForm)
private
procedure WMSysCommand(var Msg: TWMSysCommand); message WM_SYSCOMMAND;
protected
procedure CreateParams(var Params: TCreateParams); override;
end;
implementation
{$R *.dfm}
procedure TForm2.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
with Params do
begin
ExStyle := ExStyle or WS_EX_APPWINDOW;
WndParent := GetDesktopWindow;
end;
end;
procedure TForm2.WMSysCommand(var Msg: TWMSysCommand);
begin
if Msg.CmdType = SC_MINIMIZE then
ShowWindow(Handle, SW_MINIMIZE)
else
inherited;
end;
end.
(Tested with D5 and D7 on XP and Win7.)
(And yes, you may flag this as being not an answer, because it isn't: There still is a MainForm. But I dó like to think this answers the question behind the question...)
I can't speak for Delphi 5, but in Delphi 7 you can definitely run without a mainform if you're willing to get your hands dirty. I covered a lot of the details in another answer here.
Since Delphi 5 doesn't have the MainFormOnTaskbar property, you need to do the following in your dpr:
// Hide application's taskbar entry
WasVisible := IsWindowVisible(Application.Handle);
if WasVisible then
ShowWindow(Application.Handle, SW_HIDE);
SetWindowLong(Application.Handle, GWL_EXSTYLE,
GetWindowLong(Application.Handle, GWL_EXSTYLE) or WS_EX_TOOLWINDOW);
if WasVisible then
ShowWindow(Application.Handle, SW_SHOW);
// Hide the hidden app window window from the Task Manager's
// "Applications" tab. Don't change Application.Title since
// it might get read elsewhere.
SetWindowText(Application.Handle, '');
That will hide the application window, and as long as you override your form's CreateParams to set Params.WndParent := 0 each of them will have a taskbar entry of their own. Application.MainForm isn't assigned, so things like the minimize override aren't an issue, but you do have to be careful about any code that assumes MainForm is valid.
You can put your modeless forms in a dll, then they act pretty much on their own. (If you do not use the Application instance of the dll while creating them (Application.CreateForm) then Application.Mainform is nil in the dll).
Of course this might not be feasible depending on what the forms might need to do.
Actually most of what you are complaining about is in fact the design of Windows rather than the VCL. See Windows Features for all the details.
The crux of the matter is the owner property, and I mean the windows owner rather than the VCL owner.
An owned window is hidden when
its owner is minimized.
If you wish to be able to minimise the main form without other windows being hidden then you need to get on top of how owned windows work.

How do do things during Delphi form startup

I have a form one which I want to show a file open dialog box before the full form opens.
I already found that I can't do UI related stuff in FormShow, but it seems that I can in FormActivate (which I protect from being called a second time...)
However, if the user cancels out of the file open dialog, I want to close the form without proceeding.
But, a form close in the activate event handler generates an error that I can't change the visibility of the form.
So how does one do some UI related operation during form start up and then perhaps abort the form (or am I trying to stuff a function into the form that should be in another form?)
TIA
It would be best (i think) to show the file open dialog BEFORE you create and show the form. If you want to keep all code together you might add a public class procedure OpenForm() or something:
class procedure TForm1.OpenForm( ... );
var
O: TOpenDialog;
F: TForm1;
begin
O := TOpenDialog.Create();
try
// set O properties.
if not O.Execute then Exit
F := TForm1.Create( nil );
try
F.Filename := O.FIlename;
F.ShowModal();
finally
F.Free();
end;
finally
O.Free();
end;
end;
Set a variable as a condition of the opendialog and close the form on the formshow event if the flag is not set correctly.
procedure TForm1.FormCreate(Sender: TObject);
begin
ToClose := not OpenDialog1.Execute;
end;
procedure TForm1.FormShow(Sender: TObject);
begin
if ToClose then Close();
end;
or even more simply
procedure TForm1.FormShow(Sender: TObject);
begin
if not OpenDialog1.Execute then Close();
end;
If you want to keep the logic conditioning the opening self-contained in the Form, you can put a TOpenDialog in your Form and use a code like this in your OnShow event:
procedure TForm2.FormShow(Sender: TObject);
begin
if OpenDialog1.Execute(Handle) then
Color := clBlue
else
PostMessage(Handle, WM_CLOSE, 0, 0); // NB: to avoid any visual glitch use AlpaBlend
end;
If you don't need this encapsulation, a better alternative can be to check the condition before trying to show the form, for instance by embedding the Form2.Show call in a function that tests all the required conditions first.
Two Ways....
1. using oncreate and onactivate
create a global flag or even 2
var
aInitialized:boolean;
Set the flag to false in the oncreate handler.
aInitialized := false; //we have not performed our special code yet.
Inside onActivate have something like this
if not aInitialized then
begin
//our one time init code. special stuff or whatever
If successful
then set aInitialized := true
else aInitialized := false
end;
And how to close it without showing anything just add your terminate to the formshow. of course you need to test for some reason to close.. :)
Procedure Tmaindlg.FormShow(Sender: TObject);
Begin
If (shareware1.Sharestatus = ssExpired) or (shareware1.Sharestatus = ssTampered) Then
application.Terminate;
End;
In your DPR you will need to add a splash screen type effect. In my case I am showing progress as the application starts. You could also just show the form and get some data.
Code from the splash.pas
Procedure tsplashform.bumpit(str: string);
Begin
label2.Caption := str;
gauge1.progress := gauge1.progress + trunc(100 / items);
update;
If gauge1.progress >= items * (trunc(100 / items)) Then Close;
End;
Program Billing;
uses
Forms,
main in 'main.pas' {maindlg},
Splash in 'splash.pas' {splashform};
{$R *.RES}
Begin
Application.Initialize;
Application.Title := 'Billing Manager';
SplashForm := TSplashForm.Create(Application);
SplashForm.Show;
SplashForm.Update;
splash.items := 5;
SplashForm.bumpit('Loading Main...');
Application.CreateForm(Tmaindlg, maindlg);
SplashForm.bumpit('Loading Datamodule...');
Application.CreateForm(TfrmSingleWorkorder, frmSingleWorkorder);
SplashForm.bumpit('Loading SQL Builder...');
Application.CreateForm(TDm, Dm);
SplashForm.bumpit('Loading Security...');
Application.CreateForm(TSQLForm, SQLForm);
SplashForm.bumpit('Loading Reports...');
Application.CreateForm(Tpickrptdlg, pickrptdlg);
Application.Run;
End.

Resources