Recent

Author Topic: [solved] Inheriting forms  (Read 8820 times)

Joanna

  • Hero Member
  • *****
  • Posts: 1450
[solved] Inheriting forms
« on: November 10, 2023, 11:30:48 am »
I have several questions regarding inherited forms

I’ve made a customized form with tform as ancestor. The first thing I noticed is that the form doesn’t use constructor and destructor keywords for the create and destroy methods generated through the object inspector.
Why is this

Also I’m curious why when I do refactoring  to change the procedure names and the procedures are assigned to events in the object inspector it cannot seem to update procedure names there
« Last Edit: November 12, 2023, 03:51:47 pm by Joanna »

JuhaManninen

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 4715
  • I like bugs.
Re: Inheriting forms
« Reply #1 on: November 10, 2023, 05:47:20 pm »
I’ve made a customized form with tform as ancestor. The first thing I noticed is that the form doesn’t use constructor and destructor keywords for the create and destroy methods generated through the object inspector.
Why is this
You cannot create constructor or destructor through object inspector. You can only create OnCreate and OnDestroy event handlers.
Often a constructor and destructor are not needed because the inherited class takes care of it.

Quote
Also I’m curious why when I do refactoring  to change the procedure names and the procedures are assigned to events in the object inspector it cannot seem to update procedure names there
It can be considered a bug. Anyway if you do it the other way around, the names are updated. Meaning, change the name in object inspector.
Mostly Lazarus trunk and FPC 3.2 on Manjaro Linux 64-bit.

Handoko

  • Hero Member
  • *****
  • Posts: 5537
  • My goal: build my own game engine using Lazarus
Re: Inheriting forms
« Reply #2 on: November 10, 2023, 06:13:39 pm »
I'm late. While typing my answer I saw JuhaManninen already posted an answer.

-----

@OP

So, you have made a customized form, why not show us the code. When talking about code related problem, it will be much easy for others to understand if you provide the code.

I saw you mentioned frame, were you talking about TForm or TFrame?

The first thing I noticed is that the form doesn’t use constructor and destructor keywords for the create and destroy methods generated through the object inspector.

Did you mean the OnCreate event and OnDestroy event?
Those events are not methods, they are variable.

For example, this is what happened if a form is being destroyed. The OnDestroy event is stored in FOnDestroy. The FOnDestroy will be process if it is assigned.

procedure TCustomForm.DoDestroy;
begin
  try
    if Assigned(FOnDestroy) then FOnDestroy(Self);
  except
    if not HandleDestroyException then
      raise;
  end;
end;


So, OnDestroy event is not a destructor.

If you want to 'fully' understand what constructor and destructor are, you need to understand the technical thing about OOP, what is VMT and why VMT is needed. That is too technical for most programmers. In short, constructor and destructor both needed to be called once.

When you code something like MyComponent := TMyComponent.Create, the constructor will be called because the Create is the constructor. But if you drop a component using the editor, the constructor and destructor will be handled automatically by Lazarus so you don't need to manually call the constructor and destructor.

There always more than one way to do the same thing in programming. Because you didn't provide your code here I show you the example of subclassing. Below is the example of a auto close form. Hope you can learn something form it.

TMyCustomForm

Code: Pascal  [Select][+][-]
  1. unit MyCustomForm;
  2.  
  3. {$mode ObjFPC}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, StdCtrls, ExtCtrls;
  9.  
  10. type
  11.  
  12.   { TMyAutoCloseForm }
  13.  
  14.   TMyAutoCloseForm = class(TForm)
  15.   private
  16.     FCounter: Integer;
  17.     FLabel:   TLabel;
  18.     FTimer:   TTimer;
  19.     procedure OnTimerEvent(Sender: TObject);
  20.     procedure ShowInfo;
  21.   public
  22.     constructor Create(TheOwner: TComponent; Duration: Integer); overload;
  23.     destructor Destroy; override;
  24.     procedure Show;
  25.   end;
  26.  
  27. implementation
  28.  
  29. { TMyAutoCloseForm }
  30.  
  31. procedure TMyAutoCloseForm.OnTimerEvent(Sender: TObject);
  32. begin
  33.   Dec(FCounter);
  34.   ShowInfo;
  35.   if FCounter <= 0 then
  36.   begin
  37.     FTimer.Enabled := False;
  38.     Close;
  39.   end;
  40. end;
  41.  
  42. procedure TMyAutoCloseForm.ShowInfo;
  43. var
  44.   S: string;
  45. begin
  46.   case FCounter > 1 of
  47.     True:  S := ' seconds';
  48.     False: S := ' second';
  49.   end;
  50.   FLabel.Caption := 'This form will close in' + LineEnding +
  51.                     FCounter.ToString + S;
  52. end;
  53.  
  54. constructor TMyAutoCloseForm.Create(TheOwner: TComponent; Duration: Integer);
  55. begin
  56.   CreateNew(TheOwner);
  57.   Self.Left       := 150;
  58.   Self.Top        := 100;
  59.   FCounter        := Duration;
  60.   FLabel          := TLabel.Create(Self);
  61.   FLabel.Parent   := Self;
  62.   FLabel.Left     := 80;
  63.   FLabel.Top      := 80;
  64.   FTimer          := TTimer.Create(Self);
  65.   FTimer.Enabled  := False;
  66.   FTimer.Interval := 1000;
  67.   FTimer.OnTimer  := @OnTimerEvent;
  68. end;
  69.  
  70. destructor TMyAutoCloseForm.Destroy;
  71. begin
  72.   FTimer.Enabled := False;
  73.   FTimer.Free;
  74.   FLabel.Free;
  75.   inherited;
  76. end;
  77.  
  78. procedure TMyAutoCloseForm.Show;
  79. begin
  80.   inherited;
  81.   FTimer.Enabled := True;
  82.   ShowInfo;
  83. end;
  84.  
  85. end.

Mainform

Code: Pascal  [Select][+][-]
  1. unit Mainform;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, Forms, StdCtrls, ExtCtrls, MyCustomForm;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Button1: TButton;
  16.     procedure Button1Click(Sender: TObject);
  17.   end;
  18.  
  19. var
  20.   Form1: TForm1;
  21.  
  22. implementation
  23.  
  24. {$R *.lfm}
  25.  
  26. { TForm1 }
  27.  
  28. procedure TForm1.Button1Click(Sender: TObject);
  29. begin
  30.   with TMyAutoCloseForm.Create(Self, 5) do // form autoclose in 5 seconds
  31.     Show;
  32. end;
  33.  
  34. end.

Joanna

  • Hero Member
  • *****
  • Posts: 1450
Re: Inheriting forms
« Reply #3 on: November 11, 2023, 11:57:29 am »
Thanks for the answers. In response to juha ...
Usually when I change the names of procedures with events it gives me a gump saying that there are “dangling procedure references “? Do you want to remove them? I usually say yes instead of keeping. It seems easier to reassign them.

Another problem I’ve had before with controls placed at design time is it would let me change their names in the .pas file but the .lfm file would not change and it would create a horrible crash at runtime. I’ve also had to open lfm files and delete things manually many times. Hopefully these issues were fixed at some point. Yes I sometimes get a bit adventurous With inheritance at times but still...

The explanation makes sense that the procedure is triggered by the destroy event rather than being the procedure that actually destroys. This is different from tframe which I’m more familiar with.

Handoko : I’m using tform descendants in this case. This makes sense if you have created the form at design time but how about creating forms at runtime? You would need to call constructor in that case and somehow the oncreate event would need to assigned. How would that work?

Handoko

  • Hero Member
  • *****
  • Posts: 5537
  • My goal: build my own game engine using Lazarus
Re: Inheriting forms
« Reply #4 on: November 11, 2023, 12:50:17 pm »
... how about creating forms at runtime? You would need to call constructor in that case and somehow the oncreate event would need to assigned. How would that work?

OnCreate event is not constructor. Creating a from at runtime and setting its OnCreate won't cause any problem because the constructor is only called once: NewForm := TForm.Create; . The constructor is not being called in OnCreate event, just I already said OnCreate is not a constructor.

Basically, the flow of the code will be something like this:
1. Constructor is called by using TFrom.Create(self)
2. Inside the Create constructor, OnCreate event will be called if OnCreate is not null
3. If you do not call the destructor manually, destructor will be called automatically if the component has owner when the owner is being destroyed

But if you create a form using the IDE, Lazarus will automatically make sure TForm.Create will be called, without you type it manually. Let's take an easier example. If you drop a TButton on a form, Lazarus will set all the things about the button and save it in the its related *.lfm and *.lrs file. When you compile the project, the compiler will automatically generate all the necessary calls to constructors and destructors of all the components based on the *.lfm files. Try to open the *.lfm file and there you can see the button you have dropped on the form is listed in the file. The compiler uses those files to generate all the components without you type it, you only need to drag-and-drop the components and set their properties. By understanding how the compiler and the Lazarus IDE work, you will appreciate them more. They really do 'magics' for you.
« Last Edit: November 11, 2023, 01:10:44 pm by Handoko »

Joanna

  • Hero Member
  • *****
  • Posts: 1450
Re: Inheriting forms
« Reply #5 on: November 11, 2023, 01:51:20 pm »
I tried removing the formcreate procedure from the oncreate event in the object inspector but the code is still executed..not sure why that is

Handoko

  • Hero Member
  • *****
  • Posts: 5537
  • My goal: build my own game engine using Lazarus
Re: Inheriting forms
« Reply #6 on: November 11, 2023, 02:24:13 pm »
Please provide a simplified code and tell me which line it is.

Joanna

  • Hero Member
  • *****
  • Posts: 1450
Re: Inheriting forms
« Reply #7 on: November 11, 2023, 03:36:50 pm »
Please provide a simplified code and tell me which line it is.
sure its like this
Code: Pascal  [Select][+][-]
  1. TFORM_BASE_WINDOW_2023 = CLASS(TFORM)
  2.   PROCEDURE FORM_CREATE( SENDER: TOBJECT) ; VIRTUAL; // oncreate  
  3. END;
  4.  
  5. PROCEDURE TFORM_BASE_WINDOW_2023.FORM_CREATE (SENDER:TOBJECT); // on create
  6.  BEGIN
  7.  INHERITED ;//this line can't be traced into,
  8.  Name := 'Base_Window';
  9.  AlphaBlend:= TRUE;
  10.  DoubleBuffered := True;
  11.  ScaleBy(144,screen.PixelsPerInch); // to avoid crashes when user has different resolution than i created with
  12. KeyPreview:= TRUE; // to close using escape key
  13. SHOWHINT:= TRUE;
  14. AutoScroll := false;
  15.  END;  
  16.  

The "INHERITED;" line can't be traced into, I'm not sure why it is allowed to compile.
« Last Edit: November 11, 2023, 03:40:33 pm by Joanna »

cdbc

  • Hero Member
  • *****
  • Posts: 2771
    • http://www.cdbc.dk
Re: Inheriting forms
« Reply #8 on: November 11, 2023, 03:52:48 pm »
Hi
The TForm does not have a procedure named "FORM_CREATE" to inherit from!
Try to either:
Code: Pascal  [Select][+][-]
  1. TFORM_BASE_WINDOW_2023 = CLASS(TFORM)
  2.   CONSTRUCTOR CREATE(ANOWNER: TCOMPONENT); VIRTUAL;  
  3. END;
  4.  
and then:
Code: Pascal  [Select][+][-]
  1. constructor TFORM_BASE_WINDOW_2023.CREATE(ANOWNER: TComponent);
  2.  BEGIN
  3.  INHERITED Create; // this line can now be traced into,
  4.  Name := 'Base_Window';
  5.  AlphaBlend:= TRUE;
  6.  DoubleBuffered := True;
  7.  ScaleBy(144,screen.PixelsPerInch); // to avoid crashes when user has different resolution than i created with
  8. KeyPreview:= TRUE; // to close using escape key
  9. SHOWHINT:= TRUE;
  10. AutoScroll := false;
  11.  END;
  12.  
or call your constructor something else, mind you, with a virtual constructor, you're starting a new inheritance chain, so you must call inherited Create, no matter what the name might end up being...
HTH
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6/QT6 -> FPC Release -> Lazarus Release &  FPC Main -> Lazarus Main

Joanna

  • Hero Member
  • *****
  • Posts: 1450
Re: Inheriting forms
« Reply #9 on: November 11, 2023, 04:03:30 pm »
Thanks Benny.  I was just going by what it created for me in object inspector. {With an underscore added for legibility}. I wonder Why is it allowing an “inherited” referencing a non existent ancestor? Is that a bug?

JuhaManninen

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 4715
  • I like bugs.
Re: Inheriting forms
« Reply #10 on: November 12, 2023, 07:54:21 pm »
Usually when I change the names of procedures with events it gives me a gump saying that there are “dangling procedure references “? Do you want to remove them? I usually say yes instead of keeping. It seems easier to reassign them.
Change the handler name in OI and it works.

Quote
Another problem I’ve had before with controls placed at design time is it would let me change their names in the .pas file but the .lfm file would not change and it would create a horrible crash at runtime. I’ve also had to open lfm files and delete things manually many times. Hopefully these issues were fixed at some point. Yes I sometimes get a bit adventurous With inheritance at times but still...
You do it in a wrong way. You must change a control's name in OI, not in the source editor.
Mostly Lazarus trunk and FPC 3.2 on Manjaro Linux 64-bit.

Joanna

  • Hero Member
  • *****
  • Posts: 1450
Re: [solved] Inheriting forms
« Reply #11 on: November 13, 2023, 01:55:04 am »
Quote
You do it in a wrong way. You must change a control's name in OI, not in the source editor.
Yes i know, I found that out the hard way with crashes and cryptic error messages.
An error whereby a name in the object inspector/.lfm file has no corresponding name in the .pas file should not even compile in my opinion.
Is there any way to make this more user friendly for the sake of anyone new to using Lazarus? It’s an easy mistake to make even using refactoring I believe.
« Last Edit: November 13, 2023, 07:13:20 am by Joanna »

JuhaManninen

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 4715
  • I like bugs.
Re: [solved] Inheriting forms
« Reply #12 on: November 13, 2023, 06:10:10 am »
Quote
You do it in a wrong way. You must change a control's name in OI, not in the source editor.
Yes i know, I found that out the hard way with crashes and cryptic error messages.
An error whereby a name in the object inspector/.lfm file has no corresponding name in the .pas file should not even compile in my opinion.
Is there any way to make this more user friendly for the sake of anyone new to using Lazarus? It’s an easy mistake to make .
Well, an .lfm file is part of the component streaming system. It is not compiled by FPC.
True, new users can make such a mistake, but soon they learn just like you did. After that it is not a problem any more.
The published section with control names could be read-only but that would prevent making any changes like adding comments or reordering the lines. I don't think it is a good idea.
If someone finds a way to improve it, please provide a patch. I don't think it is a high priority for core developers.
Mostly Lazarus trunk and FPC 3.2 on Manjaro Linux 64-bit.

Joanna

  • Hero Member
  • *****
  • Posts: 1450
Re: [solved] Inheriting forms
« Reply #13 on: November 13, 2023, 07:32:59 am »
Interesting..
So when there is a component name mismatch the error is generated differently that explains why its so hard to understand. Is it a lazarus ide error rather than the debugger?
 I wish I had the skills to fix it. :(
 Thanks your your good work.
« Last Edit: November 13, 2023, 07:34:33 am by Joanna »

JuhaManninen

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 4715
  • I like bugs.
Re: [solved] Inheriting forms
« Reply #14 on: November 18, 2023, 09:50:44 am »
It’s an easy mistake to make even using refactoring I believe.
True, the "Rename Identifier" (F2) on the published variable does not change the name neither in .flm file nor in OI. It should. I don't know if this is reported.
In my brief test a project compiled and ran even after a component name mismatch. I don't know why exactly.
Mostly Lazarus trunk and FPC 3.2 on Manjaro Linux 64-bit.

 

TinyPortal © 2005-2018