Delphi Change main form while application is running
Asked Answered
F

9

12

I have this problem. When I hide my main form, then the taskbar icon of my application is also hidden. I saw a new question about this problem as well and the answers didn't really help. They suggested to minimize it, but I do not want to minimize the application.

Is it possible to change the main form while the application is already running?

for instance. I have two forms. when I want to hide the one form and show the other form, then the taskbar icon should stay at the taskbar and the main form should switch to the other form.

I am using Delphi XE6 and it is a VCL Forms application.

I have also seen a different old question about changing main form while runtime, but it is very old and still based on Delphi 6.

Fenugreek answered 4/9, 2014 at 13:20 Comment(7)
you can change the main form with Pointer((@Application.MainForm)^) := Form2;.Roughneck
@linluk: That is bad advice. MainForm is a property, not a variable. You cannot get the address of a property, so you are actually getting the address of the pointer that the property returns. And then you are modifying THAT pointer to point at a differet object, you are not modifying the pointer that is inside of TApplication itself.Graveyard
that really sounds dangerous :S, it worked so far, and i know that it was a dirty hack, but i will look for a better solution for my future applikations where i use it (maybe setting the variable via rtti, or something) or try to avoid the need of changing the main form :)Roughneck
the rtti solution could look like TRttiContext.Create.GetType(TApplication).GetField('FMainForm').SetValue(Application,ANewMainForm); but i am not shure if it is "safer" or "better". the advantage is that i set the value of the variable and not the one of the property, but it is still dependent of the tapplication implementation.Roughneck
@Remy - the address of the pointer that property returns is the pointer inside the TApplication since MainForm directly reads FMainForm. That statement successfully writes to FMainForm.Rodarte
@SertacAkyuk: Pointer((@Application.MainForm)^) := Form2; should be the same as Tmp := Application.MainForm; Pointer((@Tmp)^) := Form2; - modifying a temp variable, not the TApplication.FMainForm field. The only way what you say could be true is if the compiler is optimizing away the property read so the code becomes Pointer((@Application.FMainForm)^) := Form2;.Graveyard
@Remy - There aren't any temporaries involved when the getter directly refers to a field. Even documented, see TCompass example here "In the TCompass class, no action is associated with reading the Heading property; the read operation consists of retrieving the value stored in the FHeading field." Indeed, code generated for TCompass.Heading is same as the code generated for TCompass.FHeading.Rodarte
M
8

Is it possible to change the main form while the application is already running?

It is not possible to change the VCL main form whilst the program is running. This property is determined once and for all when the program starts.

One possible way to proceed for you is to arrange for the secondary form, the form that is not the main form, to have a button on the taskbar. Do that by making it unowned, or by using the WS_EX_APPWINDOW extended window style.

Update

Well, you can change Application.MainForm, but it requires you to destroy the current main form, and then create a new one.

Mordent answered 4/9, 2014 at 13:24 Comment(5)
Ok thanks. I have thought about that. Why does it hide the taskbar icon when i Hide my main form?, because my application is still running, only the form is hidden.Fenugreek
Hidden windows aren't shown on the taskbar, by design: msdn.microsoft.com/en-gb/library/windows/desktop/cc144179.aspx Minimizing the window is the easy way to have a taskbar button for a window that cannot be seenMordent
@DavidHeffernan i think it is possible to change the mainform. i know it, because i use it in one of my applications. i do it like: Pointer((@Application.MainForm)^) := Form2; and it works fine. i can close the old main form without shutting down the application and hen i close the new main form the application terminates as expected.Roughneck
@Roughneck That's clearly a gross hack. What are the consequences?Mordent
I have not run in any problems with this (so far). So i think/hope that there are no consequences.Roughneck
C
6

As David Heffernan already said it is not possible to change the Main Form of an already running application. This is the limitation of the windows itself.

What you can do is cheat and never actually change the Main Form but only make it look like you did.
How do you achieve that?

Step 1: Add code to the second Form to make its own Taskbar button

procedure TWorkForm.CreateParams(var Params: TCreateParams);
begin
  inherited;
  Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
end;

Step 2: Dynamically create the second Form just before switching to it. Upon its creation previously added code will create a new Taskbar button for your second form.

Step 3: Now hide you actual Main Form. Hiding it will also hide the Taskbar button belonging to it. So you end up still having one Taskbar button shown and it is the one belonging to your second Form.

Step 4: In order to allow your second Form to terminate your application at its closure call Close method of your true Main Form from your second Forms OnClose or OnFormCloseQuery event.
If you wanna be able to switch back to your true Main Form call Show method of your Main Form instead of Close method.

This approach allows us of swapping forms pretty quickly so only most keen users will notice short animation of Taskbar button.
NOTE: If your second for is a complex one and because of that takes some time to create you might wanna create it hidden and then once its creation process is finished show it and do the swap. Otherwise you might end up with two Taskbar buttons being shown at same time which I believe you wanna avoid.

Here is a short example:
- LoginForm is a true Main Form that is created at the application startup - WorkForm is the Form on which user will spend most of time after logging in and this one is created in login process

Login Form code:

unit ULoginForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TLoginForm = class(TForm)
    BLogIn: TButton;
    procedure BLogInClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  LoginForm: TLoginForm;

  //Global variable to tell us if we are only logging out or closing our program
  LoggingOut: Boolean;

implementation

uses Unit2;

{$R *.dfm}

procedure TLoginForm.BLogInClick(Sender: TObject);
begin
  //Create second Form
  Application.CreateForm(TWorkForm, WorkForm);
  //Hide Main Form
  Self.Hide;
  //Don't forget to clear login fields
end;

end.

Work form code:

unit UWorkForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TWorkForm = class(TForm)
    BLogOut: TButton;
    //Used in overriding forms creating parameters so we can add its own Taskbar button
    procedure CreateParams(var Params: TCreateParams); override;
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure BLogOutClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  WorkForm: TWorkForm;

implementation

uses Unit1;

{$R *.dfm}

procedure TWorkForm.BLogOutClick(Sender: TObject);
begin
  //Set to true so we know we are in the process of simply logging out
  LoggingOut := True;
  //Call close method to begin closing the current Form
  Close;
end;

procedure TWorkForm.CreateParams(var Params: TCreateParams);
begin
  inherited;
  Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
end;

procedure TWorkForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  //Check to see if we are in the process of simply logging out
  if not LoggingOut then
  begin
    //If we are not in the process of logging out close the Main Form
    LoginForm.Close;
    //and then allow closing of current form
    CanClose := True;
  end
  else
  begin
    //But if we are in the process of simply logging out show the Main Form
    LoginForm.Show;
    //Reset the LoggingOut to false
    LoggingOut := False;
    //and then alow closing of current form
    CanClose := True;
  end;
end;

end.
Centralia answered 4/9, 2014 at 15:50 Comment(2)
Never ever set a window owner to be the desktop. blogs.msdn.com/b/oldnewthing/archive/2004/02/24/79212.aspxMordent
- "This is the limitation of the windows itself." - Windows doesn't care if you have a main form or not. It doesn't even have the notion of a main form.Rodarte
G
5

It is not possible to change the Application.MainForm once it has been assigned. However, you do not need to, either. The simpliest solution to this issue would be to create a blank hidden TForm to act as the real Application.MainForm and let it manage the taskbar normally, and then you can show/hide any secondary TForm objects when needed, where your desired "MainForm" is a secondary form not the real MainForm.

Graveyard answered 4/9, 2014 at 16:32 Comment(0)
S
4

I implemented this in the same way @DavidHeffernan had already suggested and as yet have not come across any issues, it might not be the best way but it worked for me and what i was trying to achieve, which was have "normal" feeling behaviour when the user minimised their MainWork form.

Code looked similar to this:

procedure TfrmLogin.btnLoginClick(Sender: TObject);
begin
    frmMainWork := TfrmMain.Create(Application);
    try
        Pointer((@Application.MainForm)^) := frmMainWork;
        frmLogin.Hide;
        frmMainWork.ShowModal;
    finally
        Pointer((@Application.MainForm)^) := frmLogin;
        frmLogin.Show;
        FreeAndNil(frmMainWork);
    end;

end;

hope this helps :-)

Seddon answered 25/1, 2017 at 12:38 Comment(0)
C
3

You can change main form. Do a variable F: ^TForm, then set it to @Application.MainForm. After that you can set main form as via F^ := YourAnotherForm.

Corker answered 5/9, 2014 at 21:5 Comment(1)
nice and elegant solution!Mylonite
A
2

If you set Application.MainFormOnTaskbar to false in your startup routine (in the .dpr file), then the VCL will create a hidden form whose sole purpose is to provide the taskbar icon. This is an older approach and is generally not recommended, but as long as other windows are visible, it would let you hide your main form without the application disappearing from the taskbar.

You can also provide an Application.OnGetMainFormHandle event handler to change your Application.MainFormHandle at runtime (but not Application.MainForm). MainFormHandle affects stuff like the owner of modal popup dialogs.

More information on Application.MainFormOnTaskbar and the disadvantages of disabling it: This gets complicated in a hurry. The short version is in the VCL docs, which explain that several of Windows' newer features (like live taskbar thumbnails) that were introduced in Vista require MainFormOnTaskbar := True. There's a lot more background reading in this SO discussion.

Acceptable answered 4/9, 2014 at 13:33 Comment(4)
What is negative about this approach? (Changing the Application.MainFormOnTaskbar to false)Fenugreek
@DavidHeffernan - It works when I test it in XE4. The assignment has to be done in the .dpr, before any forms are created.Acceptable
Haha no, I am not sure how to do it. How do you do it?Fenugreek
@JoshKelley OK, you need another top-level window showing. My experiment was from the question earlier in the day which wanted taskbar button when no windows were showing. Your answer is a decent workaround.Mordent
H
0

I've had an additional issue working with Delphi XE2 MDI apps that are also COM servers. My main form Main calls a secondary form MenuForm which contains shared icons and popup menus for the whole app.

In my example, the app works perfectly when run standalone.

Main gets created, and then creates a MenuForm at the end of FormCreate. All good.

But when called from a COM server, Main.FormCreate is called first, but somehow MenuForm gets assigned to Application.MainForm. There is a race condition in the underlying RTL. This causes havoc when trying to create the first SDI child, as Application.MainForm is not MDI.

I tried working around this by Main.FormCreate posting a message back to itself, to delay creation of MenuForm until after Main.FormCreate has returned.

But there is still a race condition here - MenuForm was still assigned to Application.MainForm.

I was eventually able to work around this using code to poll Application.MainForm every 10ms, with a maximum of 10 seconds. I also had to remove any reference in Main to MenuForm's icon list (in the .dfm) until after MenuForm was created explicitly - otherwise MenuForm would get created implicitly at the end of MainForm.create.

Hope this helps someone!

const
  CM_INITIAL_EVENT = WM_APP + 400;


TmainForm = class(TForm)
  ...
  procedure afterCreate(var Message: TMessage); message CM_INITIAL_EVENT;
  ...
end;


procedure TmainForm.FormCreate(Sender : TObject);
begin
  ...
  ...standard init code
  ...

  postmessage( handle, CM_INITIAL_EVENT, 0, 0 );
End;


procedure TmainForm.AfterCreate(var Message: TMessage);
var
  i: Integer;
begin

  //must assign these AFTER menuform has been created
  if menuForm = nil then
  begin
    //wait for mainform to get assigned
    //wait up to 10*1000ms = 10 seconds
    for i := 0 to 1000 do
    begin
      if Application.Mainform = self then break;
      sleep(10);
    end;

    Application.CreateForm(TmenuForm, menuForm);
    menuForm.parent := self;
  end;

  //NOW we can assign the icons
  Linktothisfilterfile1.SubMenuImages := menuForm.treeIconList;
  ActionManager.Images := menuForm.treeIconList;
  allFilters.Images := menuForm.treeIconList;
  MainMenu.Images := menuForm.treeIconList;
  ...
end;
Hispaniola answered 13/8, 2016 at 0:7 Comment(0)
S
0

In the current Delphi implementation, I am sure that there are no consequences using a pointer to change Application.MainForm.

If you look into TApplication class, you can see that FMainForm is used only to check if the application has at least one form, and iterates the main loop inside the TApplication.Run method while the FMainForm exists. If you do not want to use a pointer to hack this property, you can implement your own TApplication class, like TMyApplication, copy all the routines inside it and define MainForm property to read and write

Socha answered 21/10, 2016 at 19:54 Comment(0)
S
0

Delete the *.dproj file. Delete the following line in the *.dpr file. You get rid of all your troubles :) Application.MainFormOnTaskbar := True; Sorry for my bad English.

Shinberg answered 26/3, 2021 at 13:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.