Recent

Author Topic: Best way to dynamically create main form  (Read 633 times)

simone

  • Sr. Member
  • ****
  • Posts: 253
Best way to dynamically create main form
« on: June 22, 2019, 05:44:39 pm »
Dear all, I'm investigating the best way to dynamically create main forms of GUI applications. My purpose is to build a code skeleton as basis of rad tool I'm building.

About this topic I read some interesting wikis and threads. As far as I understood, there are (at least), two approaches:

Method #1: (TForm constructor overriding and using CreateNew method)

Code: Pascal  [Select]
  1. program project1;
  2. {$mode objfpc}{$H+}
  3.  
  4. uses
  5.   Interfaces, Forms, Classes, SysUtils, Controls, Graphics, Dialogs, StdCtrls;
  6.  
  7. type
  8.   TMainForm=class(TForm)                                                                 
  9.     procedure ButtonOnClick(Sender : TObject);
  10.     constructor Create(TheOwner: TComponent); override;
  11.   end;
  12.  
  13. var
  14.   Form : TMainForm;
  15.   Button : TButton;
  16.  
  17. procedure TMainForm.ButtonOnClick(Sender: TObject);
  18. begin
  19.   ShowMessage('Ok');
  20. end;
  21.  
  22. constructor TMainForm.Create(TheOwner: TComponent);
  23. begin
  24.   inherited Create(TheOwner);
  25.                        
  26.   Button:=TButton.Create(Form);
  27.   with Button do
  28.     begin
  29.       Parent:=Form;
  30.       Caption:='Press';
  31.       Left:=50;
  32.       Top:=50;
  33.       Width:=80;
  34.       Height:=30;
  35.       OnClick:=@Form.ButtonOnClick;
  36.     end;
  37. end;
  38.  
  39. begin
  40.   Application.Scaled:=True;
  41.   Application.Initialize;
  42.   Application.CreateForm(TMainForm,Form);
  43.   Application.Run;
  44. end.
  45.  

Method #2: (without overriding TForm contructor and using Create method)
Code: Pascal  [Select]
  1. program project1;
  2. {$mode objfpc}{$H+}
  3.  
  4. uses
  5.   Interfaces, Forms, Classes, SysUtils, Controls, Graphics, Dialogs, StdCtrls;
  6.  
  7. type
  8.   TMainForm=class(TForm)
  9.     procedure ButtonOnClick(Sender : TObject);
  10.   end;
  11.  
  12. var
  13.   Form : TMainForm;
  14.   Button : TButton;
  15.  
  16. procedure TMainForm.ButtonOnClick(Sender: TObject);
  17. begin
  18.   ShowMessage('Ok');
  19. end;
  20.  
  21. begin
  22.   Application.Scaled:=True;
  23.   Application.Initialize;
  24.  
  25.   Form:=TMainForm.Create(Application);
  26.   Form.Show;
  27.  
  28.   Button:=TButton.Create(Form);
  29.   with Button do
  30.     begin
  31.       Parent:=Form;
  32.       Caption:='Press';
  33.       Left:=50;
  34.       Top:=50;
  35.       Width:=80;
  36.       Height:=30;
  37.       OnClick:=@Form.ButtonOnClick;
  38.     end;
  39.  
  40.   Application.Run;
  41. end.

Which of the two approaches is the most convenient? Thanks in advance.

RAW

  • Hero Member
  • *****
  • Posts: 794
Re: Best way to dynamically create main form
« Reply #1 on: June 22, 2019, 06:47:12 pm »
I guess the best way is to use PROJECT -> NEW PROJECT -> APPLICATION to go the standard unit-way.  :)
I cannot see where in the first example CREATENEW is used and the second example won't close the app when the window is closed and the user won't see any taskbar button.

EDIT: the second example needs at least this:
Code: Pascal  [Select]
  1. program project1;
  2. {$mode objfpc}{$H+}
  3.  
  4. uses
  5.   Interfaces, Forms, Classes, SysUtils, Controls, Graphics, Dialogs, StdCtrls;
  6.  
  7. type
  8.   TMainForm = class(TForm)
  9.     procedure ButtonOnClick(Sender : TObject);
  10.     procedure OnMyClose(Sender: TObject; var CloseAction: TCloseAction);
  11.   end;
  12.  
  13. procedure TMainForm.ButtonOnClick(Sender: TObject);
  14. begin
  15.   ShowMessage('Ok');
  16. end;
  17.  
  18. procedure TMainForm.OnMyClose(Sender: TObject; var CloseAction: TCloseAction);
  19. begin
  20.   Application.Terminate; // Or OnDestroy
  21. end;
  22.  
  23. procedure StartApp;
  24. var
  25.  Form  : TMainForm;
  26.  Button: TButton;
  27. begin
  28.   Application.Initialize;
  29.  
  30.    Form        :=TMainForm.Create(Application);
  31.    Form.OnClose:= @Form.OnMyClose;
  32.    Form.Show;
  33.  
  34.     Button:=TButton.Create(Form);
  35.      with Button do
  36.       begin
  37.         Parent:=Form;
  38.         Caption:='Press';
  39.         Left:=50;
  40.         Top:=50;
  41.         Width:=80;
  42.         Height:=30;
  43.         OnClick:=@Form.ButtonOnClick;
  44.       end;
  45.   Application.Run;
  46. end;
  47.  
  48. begin
  49.   StartApp;
  50. end.
« Last Edit: June 22, 2019, 07:27:23 pm by RAW »
Windows 7 Pro (x64 Sp1) And Windows XP Pro (x86 Sp3) - LAZARUS 2.0.4 FPC 3.0.4 - TRUNK 2.1.0 FPC 3.3.1

simone

  • Sr. Member
  • ****
  • Posts: 253
Re: Best way to dynamically create main form
« Reply #2 on: June 22, 2019, 08:43:54 pm »
I guess the best way is to use PROJECT -> NEW PROJECT -> APPLICATION to go the standard unit-way.  :)
Yes, I know, but in my context I'm building a tool that generates code, thus I can't use the standard RAD way.

I simply want to know the best practice to create main form with a programmatically approach.
 
I cannot see where in the first example CREATENEW is used
My mistake writing the post… I meant 'CreateForm'...

and the second example won't close the app when the window is closed and the user won't see any taskbar button.
You are right…

I presume the first method is preferable... Thanks for explanations
« Last Edit: June 22, 2019, 11:19:31 pm by simone »

SymbolicFrank

  • Hero Member
  • *****
  • Posts: 635
Re: Best way to dynamically create main form
« Reply #3 on: June 22, 2019, 10:32:06 pm »
The simplest way is to just create and show them.

RAW

  • Hero Member
  • *****
  • Posts: 794
Re: Best way to dynamically create main form
« Reply #4 on: June 22, 2019, 11:00:24 pm »
Quote
The simplest way is to just create and show them.
:D


This is easy to do and works, BUT not very nice !!!  :)
A second taskbar-button appears if I click on minimize

Code: Pascal  [Select]
  1. program Project1;
  2. {$mode objfpc}{$H+}
  3.  
  4. uses
  5.   {$IFDEF UNIX}{$IFDEF UseCThreads}
  6.    cthreads,
  7.   {$ENDIF}{$ENDIF}
  8.    Interfaces,
  9.    Forms, Classes, StdCtrls, Dialogs;
  10.   {$R *.res}
  11.  
  12. procedure ButtonClick(Self: TForm; Sender: TObject);
  13. begin
  14.   ShowMessage('Hello World');
  15. end;
  16.  
  17. procedure Run;
  18. var
  19.  wnd: TForm;
  20.  btn: TButton;
  21.  clk: TNotifyEvent;
  22. begin
  23.   RequireDerivedFormResource:=True;
  24.   Application.Scaled:=True;
  25.   Application.Initialize;
  26.  
  27.   wnd              := TForm.Create(Application);
  28.   wnd.SetBounds    (50, 50, 300, 200);
  29.   wnd.ShowInTaskBar:= stAlways; // main project gets also a taskbar button
  30.   wnd.Caption      := 'MY PRG';
  31.  
  32.   TMethod(clk).Code:= @ButtonClick;
  33.   TMethod(clk).Data:= wnd;
  34.  
  35.   btn          := TButton.Create(wnd);
  36.   btn.Caption  := 'CLICK ME';
  37.   btn.SetBounds(0, 0, 100, 50);
  38.   btn.OnClick  := clk;
  39.   btn.Parent   := wnd;
  40.  
  41.   wnd.ShowModal;
  42. end;
  43.  
  44. begin
  45.   Run;
  46. end.

You can use only one procedure for everything if you like // no units, no type ...  :)
There is still the taskbar button thing ... maybe a closer look into Application.CreateForm will help ?
It would be easier with the old OBJECT type or RECORD (advanced), but I guess nested methods are forbidden.  :'(
Code: Pascal  [Select]
  1. procedure ShowMainWnd(x, y, w, h: Integer);
  2. var       // main form as one proc, no units, no type, no other stuff
  3.  wnd : TForm;
  4.  btn1, btn2: TButton;
  5.  clk1, clk2: TNotifyEvent;
  6.  
  7.  // all events inside the proc
  8.  procedure Button1Click(Self: TForm; Sender: TObject);
  9.  begin
  10.    ShowMessage('Button 1');
  11.  end;
  12.  
  13.  procedure Button2Click(Self: TForm; Sender: TObject);
  14.  begin
  15.    ShowMessage('Button 2');
  16.  end;
  17.  
  18. begin
  19.   wnd          := TForm.Create(Application);
  20.   wnd.SetBounds(x, y, w, h);
  21.   wnd.Caption  := 'My Window Proc';
  22.  
  23.   TMethod(clk1).Code:= @Button1Click;
  24.   TMethod(clk1).Data:= wnd;
  25.  
  26.   btn1          := TButton.Create(wnd);
  27.   btn1.Caption  := 'Button 1';
  28.   btn1.SetBounds(0, 0, 100, 50);
  29.   btn1.OnClick  := clk1;
  30.   btn1.Parent   := wnd;
  31.  
  32.   TMethod(clk2).Code:= @Button2Click;
  33.   TMethod(clk2).Data:= wnd;
  34.  
  35.   btn2          := TButton.Create(wnd);
  36.   btn2.Caption  := 'Button 2';
  37.   btn2.SetBounds(0, 80, 100, 50);
  38.   btn2.OnClick  := clk2;
  39.   btn2.Parent   := wnd;
  40.  
  41.   wnd.ShowModal;
  42. end;

Code: Pascal  [Select]
  1. begin
  2.   RequireDerivedFormResource:=True;
  3.   Application.Scaled:=True;
  4.   Application.Initialize;
  5.   ShowMainWnd(100, 100, 400, 300);
  6. end.

This is working a lot better ...  not perfect though if you use minimize, but only one button !
Code: Pascal  [Select]
  1. begin
  2.   RequireDerivedFormResource:=True;
  3.   Application.Scaled:=True;
  4.   Application.Initialize;
  5.   Application.TaskBarBehavior:= tbSingleButton;
  6.   ShowMainWnd(0, 0, 400, 300);
  7. end.
« Last Edit: June 23, 2019, 01:56:22 am by RAW »
Windows 7 Pro (x64 Sp1) And Windows XP Pro (x86 Sp3) - LAZARUS 2.0.4 FPC 3.0.4 - TRUNK 2.1.0 FPC 3.3.1

lucamar

  • Hero Member
  • *****
  • Posts: 2075
Re: Best way to dynamically create main form
« Reply #5 on: June 23, 2019, 12:50:31 am »
In your first example--if you're going that route--please, don't reference a particular instance from inside the class!

Your constructor, for example, should look like this:

Code: Pascal  [Select]
  1. constructor TMainForm.Create(TheOwner: TComponent);
  2. begin
  3.   inherited Create(TheOwner);
  4.                        
  5.   Button:=TButton.Create(Form);
  6.   with Button do
  7.     begin
  8.       Parent := Self; {*NOT* Parent := form}
  9.       Caption:='Press';
  10.       Left:=50;
  11.       Top:=50;
  12.       Width:=80;
  13.       Height:=30;
  14.       OnClick := @ButtonOnClick; {*NOT* @Form.ButtonOnClick;}
  15.     end;
  16. end;

Otherwise, only that particular object instance will work, not any other that may be created dynamically; i.e. if someone clicks the button in say AnotherForm, it will act as if the button in Form had been clicked. Which, I assume, is not what you want, is it? ;)
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus 2.0.2/2.0.4  - FPC 3.0.4 on:
(K|L)Ubuntu 12..16, Windows XP SP3, various DOSes.

RAW

  • Hero Member
  • *****
  • Posts: 794
Re: Best way to dynamically create main form
« Reply #6 on: June 23, 2019, 02:08:54 am »
This works too ...  :)
Code: Pascal  [Select]
  1. program Project1;
  2. {$mode objfpc}{$H+}
  3.  
  4. uses
  5.   {$IFDEF UNIX}{$IFDEF UseCThreads}
  6.    cthreads,
  7.   {$ENDIF}{$ENDIF}
  8.    Interfaces,
  9.    Forms, Classes, StdCtrls, Dialogs;
  10.   {$R *.res}
  11.  
  12. procedure ShowMainWnd(x, y, w, h: Integer);
  13. var       // main form as one proc, no units, no type...
  14.  wnd : TForm;
  15.  btn1, btn2: TButton;
  16.  clk1, clk2: TNotifyEvent;
  17.  
  18.  // all events inside the proc
  19.  procedure Button1Click(Sender: TObject);
  20.  begin
  21.    ShowMessage('Button 1');
  22.  end;
  23.  
  24.  procedure Button2Click(Sender: TObject);
  25.  begin
  26.    ShowMessage('Button 2');
  27.  end;
  28.  
  29. begin
  30.   wnd          := TForm.Create(Application);
  31.   wnd.SetBounds(x, y, w, h);
  32.   wnd.Caption  := 'My Window Proc';
  33.  
  34.   TMethod(clk1).Code:= @Button1Click;
  35.   TMethod(clk2).Code:= @Button2Click;
  36.  
  37.   btn1          := TButton.Create(wnd);
  38.   btn1.Caption  := 'Button 1';
  39.   btn1.SetBounds(0, 0, 100, 50);
  40.   btn1.OnClick  := clk1;
  41.   btn1.Parent   := wnd;
  42.  
  43.   btn2          := TButton.Create(wnd);
  44.   btn2.Caption  := 'Button 2';
  45.   btn2.SetBounds(0, 80, 100, 50);
  46.   btn2.OnClick  := clk2;
  47.   btn2.Parent   := wnd;
  48.  
  49.   wnd.ShowModal;
  50. end;
  51.  
  52. begin
  53.   RequireDerivedFormResource:=True;
  54.   Application.Scaled:=True;
  55.   Application.Initialize;
  56.   Application.TaskBarBehavior:= tbSingleButton;
  57.   ShowMainWnd(0, 0, 400, 300);
  58. end.
Windows 7 Pro (x64 Sp1) And Windows XP Pro (x86 Sp3) - LAZARUS 2.0.4 FPC 3.0.4 - TRUNK 2.1.0 FPC 3.3.1

simone

  • Sr. Member
  • ****
  • Posts: 253
Re: Best way to dynamically create main form
« Reply #7 on: June 23, 2019, 10:56:46 am »
In your first example--if you're going that route--please, don't reference a particular instance from inside the class!

My mistake, due to cut&paste of that code from 'method #2', where instance 'form' is necessary, because here we are inside begin...end., outside the method of class.

This works too ...  :)

Very interesting this:
Code: Pascal  [Select]
  1.   TMethod(clk1).Code:= @Button1Click;
  2.   TMethod(clk2).Code:= @Button2Click;

Thanks...

« Last Edit: June 23, 2019, 11:17:49 am by simone »