Jump to content
Jeff Steinkamp

Minimizing Forms

Recommended Posts

I have a number of forms that are created from the main form, but those forms are created without an owner.  But if those forms are showing and we minimize the main form, all of the forms, owned by the main form or not, get minimized.  That certainly is not the behavior I would expect.  These forms are not created at Applicaiton run time, but whenever the user clicks on the menu item for those forms.  Here is a sample of the code:

<code>

begin
  dlg := TSelectContest.CreateNew(self, false);
  try
    if dlg.showmodal = mrok then
    begin
      tablename := dlg.tablename;
      TMyContest.Create(nil, tablename);
    end;

  finally
    dlg.Free
  end;

</code>

 

Tmycontest is the form that should remain visible.  The form is actually shown in the forms create procedure after some other initialization.

 

I have searched for a workaround on this but have not found anything remotely related.  Is there some way to make this happen?

 

Thanks

Share this post


Link to post

This might help https://stackoverflow.com/questions/52470476/how-to-restore-a-minimized-modal-form

 

And because i am not sure if i understand the problem exactly, i mean do you need a dedicated icon on the taskbar for these forms or not, but anyway.

 

If you try this 

  private
    procedure WMSysCommand(var Msg: TWMSysCommand); message WM_SYSCOMMAND;
..

procedure TMyContest.FormCreate(Sender: TObject);
begin
  SetWindowLong(Handle, GWL_HWNDPARENT, 0);
end;

procedure TMyContest.WMSysCommand(var Msg: TWMSysCommand);
begin
  case Msg.CmdType of
    SC_MINIMIZE:
      begin
        Exit;
      end;
    SC_MAXIMIZE:
      begin
        Exit;
      end;
  end;

  inherited;
end;

This will prevent MyContest form form minimizing and maximizing even if the border buttons are there, and even if your main is changing state.

Share this post


Link to post
11 hours ago, Jeff Steinkamp said:

I have a number of forms that are created from the main form, but those forms are created without an owner.  But if those forms are showing and we minimize the main form, all of the forms, owned by the main form or not, get minimized.  That certainly is not the behavior I would expect.  These forms are not created at Applicaiton run time, but whenever the user clicks on the menu item for those forms.  Here is a sample of the code:

<code>

begin
  dlg := TSelectContest.CreateNew(self, false);
  try
    if dlg.showmodal = mrok then
    begin
      tablename := dlg.tablename;
      TMyContest.Create(nil, tablename);
    end;

  finally
    dlg.Free
  end;

</code>

 

Tmycontest is the form that should remain visible.  The form is actually shown in the forms create procedure after some other initialization.

 

I have searched for a workaround on this but have not found anything remotely related.  Is there some way to make this happen?

 

Thanks

In all newer Delphi versions ( I think this was implemented after Delphi 7) all forms use the Application.Mainform (the first form created in the application's autocreate list) as the window owner on the API level. If that owner window is minimized Windows automatically hides all the owned forms and it also makes sure the owned forms always are displayed above the owner form in the Z order. You can change this behaviour for your secondary forms by overriding the CreateParams method.

 

procedure TFormX.CreateParams( Var params: TCreateParams );
begin
  inherited CreateParams( params );
  // The following disconnects the form from the main form in Z order
  params.WndParent := GetDesktopwindow;
  // The following gives the form its own taskbar button
  params.ExStyle := params.ExStyle  or WS_EX_APPWINDOW;
end;

The side effect is that the main form can now display on top of the secondary form and hide it. For popup forms that can be a problem. This modification also effectively disables the PopupParent of a form, since that is used in the inherited CreateParams to set the WndParent.

  • Like 1

Share this post


Link to post
14 hours ago, PeterBelow said:

In all newer Delphi versions ( I think this was implemented after Delphi 7) all forms use the Application.Mainform (the first form created in the application's autocreate list) as the window owner on the API level.

It is a bit more complicated than that.

 

The owner window that is used at the API level depends on many factors, like if the TForm has a Parent or PopupParent assigned, or the MainForm window or the currently active TForm window may be used depending on the TForm's PopupMode.

 

If the VCL decides the MainForm should be used, it will do so only if TApplication.MainFormOnTaskbar is True and the MainForm window has already been allocated, otherwise the TApplication window is used instead.

 

Even then, there are TApplication.OnGetActiveFormHandle and TApplication.OnGetMainFormHandle events which allow the app to override the VCL's decision and use a different window of the app's choosing.

 

See the implementation of TCustomForm.CreateParams(), there is a lot of decision-making going on in that method to decide which owner window to use.

Edited by Remy Lebeau

Share this post


Link to post

I must say that I am completely perplexed and confused. I do not quite understand how a form that is created without an owner is being controlled by the mainform.  Remember, this form is not being created at the application level with the Applicaiton.CreateForm syntax in the program module.  It is an available form for creation at a later time.

 

I did add this code:

 

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

But, as far as I can tell, it never gets fired.  I placed a breakpoint in the code, but it never stops on the breakpoint even though the documentation says that this gets run when the form is created. 

Here is the create code for that form:

constructor TMyContest.create(AOwner: TComponent; name: String);
begin
  inherited create(AOwner);
  Tablename := name;
  show;
end;

and here is the calling code from the main form:

 dlg := TSelectContest.CreateNew(self, false);
  try
    if dlg.showmodal = mrok then
    begin
      tablename := dlg.tablename;
      TMyContest.Create(nil, tablename);
    end;

  finally
    dlg.Free

So at this point, I have no clue how to create a form, show it and allow it to remain visible when the main form is minimized.  I've coded in Visual Studio, QT and Swing and never run into this behavior.

Share this post


Link to post

I to use this on a show modal

class procedure Taboutbox.showme;
  begin
    aboutbox := Taboutbox.Create(nil);

and this on other form now I pass the sender so that the Sender is the owner. The owner has a list of controls that it frees on closing. 

Class procedure TSCSForm.showme(const AText:string; const ATime:integer);
begin
  if not assigned(SCSForm) then
       SCSForm := TSCSForm.Create(nil); // should have used sender for the owner
  with SCSForm do
  begin
    if ATime > 0 then TimedStop := True;
    timer1.Interval:= Atime;
    Label1.caption:=Label1.caption + CR + AText;
    if not SCSForm.Showing then
    begin
      Show;

Are you using a DataModule?

Share this post


Link to post
1 hour ago, Jeff Steinkamp said:

and here is the calling code from the main form:


 dlg := TSelectContest.CreateNew(self, false);

CreateNew is AFAIK is for forms without .dfm.  That may hiding your Create! You should able to show and hide forms once created. and use windows task bar to navigate.   

Share this post


Link to post
8 minutes ago, Pat Foley said:

CreateNew is AFAIK is for forms without .dfm.  That may hiding your Create! You should able to show and hide forms once created. and use windows task bar to navigate.   

If you read the code I posted, TSelectContest is NOT the issue.  the Issue with

 TMyContest.Create(nil, tablename);

Share this post


Link to post
23 minutes ago, Pat Foley said:

I to use this on a show modal


class procedure Taboutbox.showme;
  begin
    aboutbox := Taboutbox.Create(nil);

and this on other form now I pass the sender so that the Sender is the owner. The owner has a list of controls that it frees on closing. 


Class procedure TSCSForm.showme(const AText:string; const ATime:integer);
begin
  if not assigned(SCSForm) then
       SCSForm := TSCSForm.Create(nil); // should have used sender for the owner
  with SCSForm do
  begin
    if ATime > 0 then TimedStop := True;
    timer1.Interval:= Atime;
    Label1.caption:=Label1.caption + CR + AText;
    if not SCSForm.Showing then
    begin
      Show;

Are you using a DataModule?

The form is not being shown as a modal form.  But, Yes I do have a DataModule and I have tried passing that as the owner and still get the same behavior.

Share this post


Link to post
2 minutes ago, Jeff Steinkamp said:

If you read the code I posted, TSelectContest is NOT the issue.  the Issue with


 TMyContest.Create(nil, tablename);

Try tablename := TMyContest.Create(Application); 

Share this post


Link to post
1 hour ago, Jeff Steinkamp said:

I do not quite understand how a form that is created without an owner is being controlled by the mainform.  Remember, this form is not being created at the application level with the Applicaiton.CreateForm syntax in the program module.  It is an available form for creation at a later time.

A VCL owner and an API owner are two completely different things.  The VCL's owner manages object lifetime.  The API's owner manages window display and Z-ordering.

1 hour ago, Jeff Steinkamp said:

I did add this code:


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

But, as far as I can tell, it never gets fired.

Did you perhaps forget the 'override' directive on the declaration of CreateParams() in the TMyContest class?

type
  TMyContest = class(TForm)
    ...
  protected
    procedure CreateParams(var params: TCreateParams); override;
                                                       ^^^^^^^^^
    ...
  end;
18 minutes ago, Pat Foley said:

CreateNew is AFAIK is for forms without .dfm.  That may hiding your Create!

The Create() and CreateNew() constructors are just to create the TForm object.  The CreateParams() method is called by the TWinControl.Handle property getter whenever the HWND window is being created at the API level.  TForm object construction and HWND window creation are two different things.

Edited by Remy Lebeau

Share this post


Link to post
11 minutes ago, Remy Lebeau said:

A VCL owner and an API owner are two completely different things.  The VCL's owner manages object lifetime.  The API's owner manages window display and Z-ordering.

Did you perhaps forget the 'override' directive on the declaration of CreateParams() in the TMyContest class?


type
  TMyContest = class(TForm)
    ...
  protected
    procedure CreateParams(var params: TCreateParams); override;
                                                       ^^^^^^^^^
    ...
  end;

The Create() and CreateNew() constructors are just to create the TForm object.  The CreateParams() method is called by the TWinControl.Handle property getter whenever the HWND window is being created at the API level.  TForm object construction and HWND window creation are two different things.

Yes I did forget the Override directive.  I have added that, and that procedure is now being called.  I also now get the icon for that form in the taskebar.  But, when I minimize the main form, the contest form still minimized, and the icon is removed from the task bar. I cannot believe this is such a freaking goat rope!  How is it that a form that is created with NO owner is being manipulated by the main form.

Share this post


Link to post
9 minutes ago, Remy Lebeau said:

The CreateParams() method is called by the TWinControl.Handle property getter whenever the TForm's HWND window is being created

I did look at the source of TCustomForm to be sent to TScrollingControl did not find the stuff.  I did stumble into a Menu & decider a while back 🙂

 

7 hours ago, Remy Lebeau said:

there is a lot of decision-making going on in that method to decide which owner window to use.

 

Share this post


Link to post
10 minutes ago, Jeff Steinkamp said:

But, when I minimize the main form

Try hiding the mainform

Share this post


Link to post
1 hour ago, Pat Foley said:

Try hiding the mainform

What is that going to do?  That is NOT what the user has selected to do.  I can intercept the minimize action and change it to hide, but then how is the user going to get that back as hiding a form does not place it on the task bar.

Share this post


Link to post

When the main form is minimized it will hide the other forms so users can work on other Applications. Clicking on the taskbar will surface the windows again as they were before the application was minimized. It may be the main form was set in the object inspector to be topmost window. And/or bring secondary form to front and then just show over the other forms virtually hiding them. Then simply hide said form and other windows will still be there. 

Share this post


Link to post
4 hours ago, Jeff Steinkamp said:

That is NOT what the user has selected to do.  I can intercept the minimize action and change it to hide, but then how is the user going to get that back as hiding a form does not place it on the task bar.

Have you tried my snippet above ?

 

Here more little more complete one

Main form

type
  TMainForm = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

uses
  uMyContest;

procedure TMainForm.Button1Click(Sender: TObject);
begin
  //MyContestForm.Show;   // or
  with MyContestForm.Create({Self} nil) do
    Show;
end;

end.

And your ContestForm

unit uMyContest;

interface

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

type
  TMyContestForm = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    procedure WMSysCommand(var Msg: TWMSysCommand); message WM_SYSCOMMAND;
  public
    { Public declarations }
  end;

var
  MyContestForm: TMyContestForm;

implementation

{$R *.dfm}

procedure TMyContestForm.FormCreate(Sender: TObject);
begin
  SetWindowLong(Handle, GWL_HWNDPARENT, 0);
end;

procedure TMyContestForm.WMSysCommand(var Msg: TWMSysCommand);
begin
  case Msg.CmdType of
    SC_MINIMIZE:
      begin
        Exit;       // Do nothing
      end;
    SC_MAXIMIZE:
      begin
        Exit;       // Do nothing
      end;
  end;

  inherited;
end;

end.

Contest form now will not hide or minimize when main form is minimized, also it have an icon on taskbar.

Other than naming i didn't touch anything in the designer properties like border icons .

 

 

Share this post


Link to post

I'll play with this and see what happens. Right now, what I am doing is creating the contest form with the main form as the owner, hiding the main form and launching the contest form as a Modal form.  When the user closes the contest form and we retune with the modal result, I'm making the main form visible again.    I'll discuss this with my team of user testers and see what they say.  I'll test this code above and if this works, we'll use it.  Thanks!!

Share this post


Link to post
11 hours ago, Kas Ob. said:

procedure TMyContestForm.FormCreate(Sender: TObject);
begin
  SetWindowLong(Handle, GWL_HWNDPARENT, 0);
end;

 

 

FYI, your manual setting is not guaranteed to be persistent.  The Form's Handle CAN be recreated dynamically during the Form's s lifetime, which would lose your manual setting.  If you want to customize the Form's owner window, you need to override the Form's CreateParams() method so your manual value takes affect on every HWND that the Form creates, eg:

type
  TMyContestForm = class(TForm)
    ...
  protected
    procedure CreateParams(var params: TCreateParams); override;
    ...
  end;

procedure TMyContestForm.CreateParams(var params: TCreateParams);
begin
  inherited CreateParams(params);
  params.WndParent := 0; // or GetDesktopWindow()
end;

 

Edited by Remy Lebeau
  • Like 2

Share this post


Link to post

I think we have beat this horse enough.  I have two solutions that will work, and I have my beta testers evaluating each to determine which way we are going to go.  Many thanks to everyone who provide input.

  • Like 1

Share this post


Link to post
3 hours ago, Jeff Steinkamp said:

I have two solutions that will work, and I have my beta testers evaluating each to determine which way we are going to go. 

It would be nice if share with us your final result for best solution, and thank you in advance.

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×