How to not have a MainForm in Delphi?
Asked Answered
T

6

5

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

Tildy answered 21/10, 2010 at 13:51 Comment(1)
If read-only property is the only problem that keeps you from achieving your goal you could also try modifying Forms.pas to make it a read-write property.Phyllode
H
10

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;
Hispaniola answered 21/10, 2010 at 19:38 Comment(5)
When, and how, do you trigger the creation of the real MainForm? Who do you set as the owner of the form? Application? BlankHiddenForm? nil? How do you show the new MainForm, and when? When do you call BlankHiddenForm.Hide? i cannot hide my dummy form during OnShow (it's not allowed). And how do i get focus to my secondary MainForm?Tildy
Create it whenever you want. Assign whatever Owner you want to it. Show it however you want. None of that matters, since your code is managing that Form, so you can do whatever is comfortable for you. The simplest thing to do is to auto-create it (which will set the Application as the owner) and then Show() it when ready. As for hiding the assigned MainForm, just set the TApplication.ShowMainForm property to False before calling TApplication.Run(), don't try to Hide() it manually.Hispaniola
A guy managed to answer this question over here #6229466 It's actually very tricky and not obvious at all. Should i create my form during OnCreate? OnShow? OnActivate? Should i start 0ms timer? Do i start it during OnCreate? OnShow? OnActivate? Should the real form be created as Application.CreateForm? .Create(Application)? .Create(Self)? .Create(nil)? Do i call .Show? When do i free it? Do i call .ShowModal; .Free? There are not novel questions.Tildy
And most of all, now that my main form is invisible: How does the user close the application?Tildy
sigh obviously I have to write some code for you. I have edited my reply to include code samples.Hispaniola
P
5

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. :-)

Predatory answered 21/10, 2010 at 14:9 Comment(9)
i would love to spent a few thousand dollars every year or two - but its not going to happen. We've mostly switched to free Visual Studio. i would love Borland to release a free version of Delphi. But accepted answer you can't, since it is a valid (and i assume correct) answer.Tildy
Ian: It wouldn't have been a few thousand dollars every year if you'd upgraded (D5 days); the Pro version upgrade cost was around $300 then. The current Pro version has gone up to around $400, but Software Assurance (around the same price) gives you free upgrades for 12 months at a time. A free version would be great, if Embarcadero had the billions in revenue from other product lines to support it. Since they're not MS, they don't.Predatory
There is also the non-zero cost in converting and testing applications in the new delphi version (e.g. changes with TThread and synchronize, new components, dsgnintf.pas/dsgnintf.dcu/DesignInterface/ DesignerInterfaces/DesigntimeInterfaces, Types/Variants, string/AnsiString/WideString/UnicodeString)Tildy
That non-zero cost in converting and testing was much less than the non-zero cost of switching your entire development from one language/IDE (Delphi) to an entirely new one (VS/whatever language you chose). :-)Predatory
Obviously we didn't switch everything (only new projects) since i'm still asking about Delphi 5.Tildy
I figured that, but you still had the non-zero cost of switching IDE/language for new projects, right?Predatory
Since you're talking about D5, what was VS version on D5 release? Mayber 5 or 6. Since VS.NET changed all the things, it means that an equivalent switch on MS land would cost more since it'll (or maybe almost) be a complete rewrite of the application. From this POV, a switch between Delphi versions would be a breeze in cost terms.Samaria
@Ken White, @Fabricia: i don't disagree with you. While Delphi 6 was a god-awful mess, Delphi 7 is great - and BorImpEmbargadero has been having a mess trying to get it's Visual Studio clone of Delphi working... But i wish we would have kept upgrading. But it's not up to me. And i can't really justify to anyone why their next update will take so long and (oops) have bugs. And i can't force everyone else to upgrade because i want to. And the free price tag of VS is something i can't really argue with - considering there's been n sub-par versions of Delphi since DFDN.Tildy
Have to agree with Ken and Fabricia. I took a Delphi 7 project with about 800.000 lines of code, using third party components from Report Builder, Woll2Woll and others and migrated it to Delphi 2007. It cost me about a day and I had to modify a few uses-clauses and effectively less that 40 lines of code. Hardly the costs and efforts I was worrying about. :)Helmer
K
4

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...)

Kantar answered 3/6, 2011 at 16:36 Comment(0)
T
2

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.

Though answered 3/6, 2011 at 18:46 Comment(0)
U
0

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.

Utta answered 21/10, 2010 at 14:51 Comment(5)
There are terrible hazzards associated with exposing a class from a dll - you cannot add or remove any private members or methods.Tildy
I did not quite understand what you mean by adding private members, but of course this kind of design would impose limitations. As I said in the last paragraph it might just not be feasible. BTW, I have some old projects (D3) with only one line of code in the executable: ShowFormInDll;. Then, 'Application.MainForm' is effectively nil for that project.Utta
It's the hazzards of having a class in a DLL. Anyone calling methods on an object exposed from a dll have to know the declaration of the form in the dll. This is so that when it calls a method it calls the proper offet in the Virtual Method Table (VMT). If your form adds a private member (e.g. an integer, which is four bytes), anyone calling that form will now crash, since they will four-bytes off when looking at,what they think, is the VMT. The application and dll have to always be compiled together. If you don't, you will get crashes. These issues are the reason COM was invented.Tildy
@Ian - Ok, then put your forms in a COM dll. <g> Seriously though, I never thought about operating directly on a dll object from the application, thanks for explaining.Utta
Ugh, and now have to register COM classes every time i want to ship a version? Delphi's single-executable is a huge feature - which unfortunately Microsoft doesn't share.Tildy
J
0

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.

Jut answered 3/6, 2011 at 17:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.