Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2010-06

WriteComponent not outputting a hierarchy of components?

20 messages · 5 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Graeme Geldenhuys

I'm using FPC 2.4.1

I'm experimenting with streaming (read & write) components to build a form and runtime from an external file (something like GTK2 has with Glade). I have a ReadForm() method very similar to the SaveForm below, and it works pretty well. All components placed directly on the form are loaded and displayed correctly, except if you have nested components. eg: a Panel on the Form and then components inside the Panel.

I thought that maybe I am creating the *.frm files (text object format of form) incorrectly, so wrote the code below to see what the RTL does automatically for me.

procedure SaveForm(AForm: TComponent);
var
  f, f2: TStream;
  Filename: string;
  TextStream, BinStream: TStream;
begin
  Filename    := LowerCase(Copy(AForm.ClassName, 2, 255)) + '.frm';
  BinStream   := TMemoryStream.Create;
  TextStream  := TFileStream.Create(Filename, fmCreate);
  BinStream.WriteComponent(AForm);
  BinStream.Position := 0;
  ObjectBinaryToText(BinStream, TextStream);
  TextStream.Free;
  BinStream.Free;
end;

For some reason a call: SaveForm(self)
... in a button click, creates the *.frm file, but in only writes the TfpgForm published properties. It doesn't write any information about the other components on the form, or nested components.

-------[ actual content of *.frm file ]----------------------- object CheckBoxForm: TCheckBoxForm
Height = 400
Left = 520
MaxHeight = 0
MaxWidth = 0
MinHeight = 32
MinWidth = 32
ModalResult = mrNone
Sizeable = True
Top = 166
Width = 400
WindowPosition = wpOneThirdDown
WindowTitle = 'Check box test'
end
----------------[ end ]----------------------

What is WriteComponent() looking for to traverse a Form?

I quickly put together the code shown below. It traverses components on the form and writes each components name width indentation to show relationships to the console. I also use ComponentCount and Components[], and the output looks fine (output also shown below). So I really don't know what WriteComponent() is struggling with. Any clues?

------------------------------------
procedure TTestForm.Button1Click(Sender: TObject);
var
  ind: string;
  procedure TraverseComponents(AComponent: TComponent);
  var
    p: string;
    i: integer;
  begin
    p := ind;
    for i := 0 to AComponent.ComponentCount-1 do
    begin
      writeln(ind + AComponent.Components[i].Name);
      if AComponent.Components[i].ComponentCount > 0 then
      begin
        ind := ind + '  ';
        TraverseComponents(AComponent.Components[i]);
        ind := p;
      end;
    end;
  end;
begin
//  SaveForm(self);
  ind := '';
  TraverseComponents(self);
end;
------------------------------------

-------[ output of above code ]-----------
CheckBox1
CheckBox2
CheckBox3
GroupBox1
Label1
Edit1
Button1
--------------------

Regards,
- Graeme -

fpGUI Toolkit - a cross-platform GUI toolkit using Free Pascal http://opensoft.homeip.net/fpgui/
Michael Van Canneyt
to Graeme Geldenhuys
Graeme Geldenhuys wrote 13 lines
I'm using FPC 2.4.1 I'm experimenting with streaming (read & write) components to build a form and runtime from an external file (something like GTK2 has with Glade). I have a ReadForm() method very similar to the SaveForm below, and it works pretty well. All components placed directly on the form are loaded and displayed correctly, except if you have nested components. eg: a Panel on the Form and then components inside the Panel. I thought that maybe I am creating the *.frm files (text object format of form) incorrectly, so wrote the code below to see what the RTL does automatically for me.

[]

----------------[ end ]---------------------- What is WriteComponent() looking for to traverse a Form?

You must override GetChildren.

In the LCL, TWinControl overrides GetChildren to write child controls.

It is documented.

Michael.

Graeme Geldenhuys
to Michael Van Canneyt
Michael Van Canneyt wrote 6 lines
You must override GetChildren. In the LCL, TWinControl overrides GetChildren to write child controls. It is documented.

But why is GetChildren needed? Why can't TWriter use ComponentCount and Components[] like I have done in the TraverseComponents() recursive procedure?

Also I saw this comment in LCL's TCustomForm.GetChildren

" Adds component to children list which have no parent. (TWinControl only lists components with parents) "

Is that still true? In fpGUI I set Owner and Parent. What is different in fpGUI (compared to LCL or VCL) is that Owner = Parent, always. I don't see a need for them to be different - one widget can just as easily manage the lifespan of another widget. Re-parenting a widgets also works in fpGUI, by simply transferring the ownership to the new parent widget (inserting into Components[] array).

Regards, - Graeme - _______________________________________________ fpGUI - a cross-platform Free Pascal GUI toolkit http://opensoft.homeip.net/fpgui/
Michael Van Canneyt
to Graeme Geldenhuys
Graeme Geldenhuys wrote 11 lines
On 3 June 2010 17:24, Michael Van Canneyt wrote: > > You must override GetChildren. > > In the LCL, TWinControl overrides GetChildren to write child controls. > > It is documented. But why is GetChildren needed? Why can't TWriter use ComponentCount and Components[] like I have done in the TraverseComponents() recursive procedure?

Because Borland decided otherwise.

Actually, there is a good reason: some components create internal components they own, and which they don't want to have streamed. The 'GetChildren' allows you to control what gets streamed and what not.

" Adds component to children list which have no parent. (TWinControl only lists components with parents) "
Show quoted text 7 lines
Is that still true? In fpGUI I set Owner and Parent. What is different in fpGUI (compared to LCL or VCL) is that Owner = Parent, always. I don't see a need for them to be different - one widget can just as easily manage the lifespan of another widget. Re-parenting a widgets also works in fpGUI, by simply transferring the ownership to the new parent widget (inserting into Components[] array).

In LCL the owner is always the form. Otherwise the form variables would not work.

Michael.

Mattias Gaertner
to Michael Van Canneyt
Show quoted text 22 lines
On Thu, 3 Jun 2010, Graeme Geldenhuys wrote: > On 3 June 2010 17:24, Michael Van Canneyt wrote: >> >> You must override GetChildren. >> >> In the LCL, TWinControl overrides GetChildren to write child controls. >> >> It is documented. > > But why is GetChildren needed? Why can't TWriter use ComponentCount > and Components[] like I have done in the TraverseComponents() > recursive procedure? Because Borland decided otherwise. Actually, there is a good reason: some components create internal components they own, and which they don't want to have streamed. The 'GetChildren' allows you to control what gets streamed and what not.

For example a TOpenDialog is not in Controls, so it must be put into GetChildren.

Show quoted text 14 lines
> " Adds component to children list which have no parent. > (TWinControl only lists components with parents) " > > Is that still true? In fpGUI I set Owner and Parent. What is > different in fpGUI (compared to LCL or VCL) is that Owner = Parent, > always. I don't see a need for them to be different - one widget can > just as easily manage the lifespan of another widget. Re-parenting a > widgets also works in fpGUI, by simply transferring the ownership to > the new parent widget (inserting into Components[] array). In LCL the owner is always the form. Otherwise the form variables would not work.

Yes, and no.
Think about Frames, Ancestors and special created components (e.g. TSynEdit creates some components owned by itself).

Graeme,
you might want to take a look at
lazarus/designnonlcl/mywidgetset.pas

Mattias

Michael Van Canneyt
to Mattias Gaertner
Mattias Gaertner wrote 48 lines
On Thu, 3 Jun 2010 20:38:26 +0200 (CEST) Michael Van Canneyt wrote: > > > On Thu, 3 Jun 2010, Graeme Geldenhuys wrote: > >> On 3 June 2010 17:24, Michael Van Canneyt wrote: >>> >>> You must override GetChildren. >>> >>> In the LCL, TWinControl overrides GetChildren to write child controls. >>> >>> It is documented. >> >> But why is GetChildren needed? Why can't TWriter use ComponentCount >> and Components[] like I have done in the TraverseComponents() >> recursive procedure? > > Because Borland decided otherwise. > > Actually, there is a good reason: some components create internal > components they own, and which they don't want to have streamed. > The 'GetChildren' allows you to control what gets streamed and > what not. For example a TOpenDialog is not in Controls, so it must be put into GetChildren. >> " Adds component to children list which have no parent. >> (TWinControl only lists components with parents) " > > >> >> Is that still true? In fpGUI I set Owner and Parent. What is >> different in fpGUI (compared to LCL or VCL) is that Owner = Parent, >> always. I don't see a need for them to be different - one widget can >> just as easily manage the lifespan of another widget. Re-parenting a >> widgets also works in fpGUI, by simply transferring the ownership to >> the new parent widget (inserting into Components[] array). > > In LCL the owner is always the form. Otherwise the form variables > would not work. Yes, and no. Think about Frames, Ancestors and special created components (e.g. TSynEdit creates some components owned by itself).

I deliberately didn't mention these cases ;)

Michael.

Marco van de Voort
to Michael Van Canneyt

In our previous episode, Michael Van Canneyt said:

Show quoted text 8 lines
>> In LCL the owner is always the form. Otherwise the form variables >> would not work. > > Yes, and no. > Think about Frames, Ancestors and special created components (e.g. > TSynEdit creates some components owned by itself). I deliberately didn't mention these cases ;)

labelededit ?

Graeme Geldenhuys
to Michael Van Canneyt: Because Borland decided otherwise. Actually, there is a good reason: some components…
Michael Van Canneyt wrote 6 lines
Because Borland decided otherwise. Actually, there is a good reason: some components create internal components they own, and which they don't want to have streamed. The 'GetChildren' allows you to control what gets streamed and what not.

Well, isn't that what the 'stored' specifier could be used for too, to know if a property should be stored or not. It just looks like a rather odd implementation (broken by default) for something that is so easily accomplished with a recursive procedure. All the required functionality (ComponentCount, Components[]) is already in place.

In LCL the owner is always the form. Otherwise the form variables would not work.

I know that, and the same think applies to VCL. But again, I don't see why Borland had to do that. Like fpGUI proves, it's not even required to have Owner and Parent. fpGUI can easily remove Parent and just use Owner and work perfectly. At the moment in fpGUI, Parent is just for convenience to VCL/LCL developers - it's just an alias for Owner.

Regards, - Graeme - _______________________________________________ fpGUI - a cross-platform Free Pascal GUI toolkit http://opensoft.homeip.net/fpgui/
Graeme Geldenhuys
to Mattias Gaertner: On Thu, 3 Jun 2010 20:38:26 +0200 (CEST) For example a TOpenDialog is not in Controls, so…
Mattias Gaertner wrote
For example a TOpenDialog is not in Controls, so it must be put into GetChildren.

Sorry, I don't understand. I don't have VCL code here, only LCL & CLX code, but TOpenDialog descends from TCustomDialog, which descends from TDialog, which descends from TComponent. TComponent can be streamed just like any other TComponent or TWinControl descendants, and it will be listed in the parent control's Components[] array.

As a test I quickly created a LCL application and used the TraverseComponents() procedure I listed earlier. TOpenDialog was dropped on the form with a few other controls, and indeed (as I expected) it was listed in the Components[] array. Here is the LCL test app output.

-------------------------------
tmp $ ./project1
Label1
Button1
Memo1
GroupBox1
Edit1
CheckBox1
Button2
OpenDialog1
-------------------------------

Yes, and no. Think about Frames, Ancestors and special created components (e.g. TSynEdit creates some components owned by itself).

And also proves my earlier paint that you don't actually need Owner and Parent, and can indeed get away with only Owner (like fpGUI). I really can't see why Borland had to introduce both properties when one would have sufficed.

you might want to take a look at lazarus/designnonlcl/mywidgetset.pas

OK, my Lazarus at home is a bit old, so might not have that code. Otherwise I'll take a look
Regards,
- Graeme -

_______________________________________________
fpGUI - a cross-platform Free Pascal GUI toolkit
http://opensoft.homeip.net/fpgui/

Michael Van Canneyt
to Graeme Geldenhuys: Well, isn't that what the 'stored' specifier could be used for too, to know if a property…
Graeme Geldenhuys wrote 10 lines
On 3 June 2010 20:38, Michael Van Canneyt wrote: > > Because Borland decided otherwise. > > Actually, there is a good reason: some components create internal components > they own, and which they don't want to have streamed. > The 'GetChildren' allows you to control what gets streamed and what not. Well, isn't that what the 'stored' specifier could be used for too, to know if a property should be stored or not.

No, because there may not be a property. You can perfectly have a child component without a property to access it.

It just looks like a rather odd implementation (broken by default) for something that is so easily accomplished with a recursive procedure. All the required functionality (ComponentCount, Components[]) is already in place.

Once more, you need a way to distinguish between owned components that must be streamed, and components that must not be streamed. The 'Stored' modifier is not suitable because there may not be a property to access the component. Plenty of examples around for that.

Show quoted text 8 lines
> In LCL the owner is always the form. Otherwise the form variables > would not work. I know that, and the same think applies to VCL. But again, I don't see why Borland had to do that. Like fpGUI proves, it's not even required to have Owner and Parent. fpGUI can easily remove Parent and just use Owner and work perfectly. At the moment in fpGUI, Parent is just for convenience to VCL/LCL developers - it's just an alias for Owner.

Personally, I think that this is mixing 2 concepts:

Owner/Owned is a matter of lifetime and memory management of objects. Parent/Child is a visual relationship.

They are separate concepts, and Borland decided to reflect this in the streaming system. (apart from the need to be able to exclude children from being streamed).

I suppose there was a reason why they did this.
I once had a discussion about it with a Borland employee, and the way he made a point of it makes me believe there is a reason for it. We simply don't know it.

Michael.

Graeme Geldenhuys
to Marco van de Voort: In our previous episode, Michael Van Canneyt said: labelededit ?
Marco van de Voort wrote
> > I deliberately didn't mention these cases ;) labelededit ?

Huh? I added a LabelEdit to my test app mentioned in my reply to Mattias. Here is the output, and as you can see the internal label in the LabelEdit is listed via Components[] array too.

--------------------------------
$ ./project1
Label1
Button1
Memo1
GroupBox1
Edit1
CheckBox1
Button2
LabeledEdit1
SubLabel
OpenDialog1
--------------------------------

BTW:
The reason for the Edit1 and CheckBox1 not being indented because they are actually children of GroupBox1 is due to a old z-order bug in GTK2 (if I remember correctly). Like I mentioned, this copy (at home) is a rather old Lazarus.

Regards, - Graeme - _______________________________________________ fpGUI - a cross-platform Free Pascal GUI toolkit http://opensoft.homeip.net/fpgui/
Mattias Gaertner
to Graeme Geldenhuys: Sorry, I don't understand. I don't have VCL code here, only LCL & CLX code, but…
Show quoted text 10 lines
On 3 June 2010 20:53, Mattias Gaertner wrote: > > For example a TOpenDialog is not in Controls, so it must be put into > GetChildren. Sorry, I don't understand. I don't have VCL code here, only LCL & CLX code, but TOpenDialog descends from TCustomDialog, which descends from TDialog, which descends from TComponent. TComponent can be streamed just like any other TComponent or TWinControl descendants, and it will be listed in the parent control's Components[] array.

TWinControl.GetChildren only lists the components in the "Controls" property. This creates the Parent/Child hierarchy in the lfm files. A TOpenDialog is listed because TCustomForm.GetChildren adds non controls.
If you don't use the Parent hierarchy of TReader/TWriter your files will look like a list instead of a tree.
Nested components will create a tree.
I other words: There are two tree like structures: Owner and Parent. Parent is optional and will make your stream files more readable.

Show quoted text 9 lines
[...] > Yes, and no. > Think about Frames, Ancestors and special created components (e.g. > TSynEdit creates some components owned by itself). And also proves my earlier paint that you don't actually need Owner and Parent, and can indeed get away with only Owner (like fpGUI). I really can't see why Borland had to introduce both properties when one would have sufficed.

Readability.

Show quoted text 5 lines
> you might want to take a look at > lazarus/designnonlcl/mywidgetset.pas OK, my Lazarus at home is a bit old, so might not have that code. Otherwise I'll take a look it exists since October.

Mattias

Mattias Gaertner
to Graeme Geldenhuys: Huh? I added a LabelEdit to my test app mentioned in my reply to Mattias. Here is the…
Show quoted text 11 lines
On 3 June 2010 21:22, Marco van de Voort wrote: >> >> I deliberately didn't mention these cases ;) > > labelededit ? Huh? I added a LabelEdit to my test app mentioned in my reply to Mattias. Here is the output, and as you can see the internal label in the LabelEdit is listed via Components[] array too.

Marco just gave another example where the Owner is not the form.

Show quoted text 19 lines
-------------------------------- $ ./project1 Label1 Button1 Memo1 GroupBox1 Edit1 CheckBox1 Button2 LabeledEdit1 SubLabel OpenDialog1 -------------------------------- BTW: The reason for the Edit1 and CheckBox1 not being indented because they are actually children of GroupBox1 is due to a old z-order bug in GTK2 (if I remember correctly). Like I mentioned, this copy (at home) is a rather old Lazarus.

No. It is because you only use the Owner tree instead of both the Owner and Parent tree.

Mattias

Graeme Geldenhuys
to Michael Van Canneyt: No, because there may not be a property. You can perfectly have a child component without…
Michael Van Canneyt wrote 6 lines
> > Well, isn't that what the 'stored' specifier could be used for too, to > know if a property should be stored or not. No, because there may not be a property. You can perfectly have a child component without a property to access it.

Sorry, I didn't mean for storing at the property level, I meant a public boolean function (eg: function MustBeStored: boolean; override;) at component level that TWriter could use to query if it should store that component or not. Then at the component end you could simply override MustBeStored and return false (default could have been True - meaning by default it is stored/streamed).

This seems much simpler than the GetChildren idea and duplicating a loop through components and various class levels. But I guess Borland developers smoking whatever they did at the time, couldn't think of anything better. And now we all have to live with that fate in FPC too. :-/

Personally, I think that this is mixing 2 concepts: Owner/Owned is a matter of lifetime and memory management of objects. Parent/Child is a visual relationship.

Two concepts that work exactly in parallel, so one property could have served both purposes. And as such a lot of code in LCL and fpGUI components proves, one TComponent (widget) can just as easily manage lifetime instead of TForm. And the hierarchy of lifetime management is exactly in parallel with the visual aspect too.

Again another botched idea from Borland.

Anyway, now we are detracting from my initial question. But I guess this is yet another pointless issue I raise, because "delphi compatibility" is going to be thrown in my face shortly.

I once had a discussion about it with a Borland employee, and the way he made a point of it makes me believe there is a reason for it. We simply don't know it.

Like I said: No idea what they have been smoking. :) If one can't clearly describe the reason for some design, it probably means the design is faulty. That would be something new either, Borland made many mistakes.

To get back to the point. So must I now override GetChildren for every single component in fpGUI that can contain child components and do the loopy Components[] array thing? Or do I only need to override GetChildren in TfpgBaseForm - the top level window/container of all other components? BTW: In fpGUI TfpgForm can act as both a TForm and TFrame.

Regards, - Graeme - _______________________________________________ fpGUI - a cross-platform Free Pascal GUI toolkit http://opensoft.homeip.net/fpgui/
Graeme Geldenhuys
to Mattias Gaertner: On Thu, 3 Jun 2010 22:32:38 +0200 TWinControl.GetChildren only lists the components in…
Mattias Gaertner wrote 6 lines
I other words: There are two tree like structures: Owner and Parent. Parent is optional and will make your stream files more readable. [...] Readability.

I can't think that's the reason. D1 thru D5 (or D7) saved dfm files in binary format. So there was no readability to speak of.

Also the only people that actually look (and care) about the dfm/lfm files are the IDE designers. Normal developers almost never look inside those files (unless something major screwed up - in which case they should have made backups). In the days of binary dfm files they were screwed either way - no manual fixing of the files.

AFAIR it exists since October.

I think this copy is from early to mid 2009. I know, it's time I upgrade. :)

Regards, - Graeme - _______________________________________________ fpGUI - a cross-platform Free Pascal GUI toolkit http://opensoft.homeip.net/fpgui/
Graeme Geldenhuys
to Mattias Gaertner: On Thu, 3 Jun 2010 22:38:53 +0200 Marco just gave another example where the Owner is not…
Mattias Gaertner wrote
Marco just gave another example where the Owner is not the form.

Ok, sorry. I miss interpreted his email.

No. It is because you only use the Owner tree instead of both the Owner and Parent tree.

Well, in the Form Designer of my copy of Lazarus, I can drag the Edit1 child component outside the GroupBox without it even being clipped. So something is majorly screwed in that version of Lazarus anyway.

Coming back to your previous statement about Controls property. I always wondered what was the point of having a Controls and Components property. But now you cleared up that one for me. Still a crap (unnecessary) design from Borland.

Regards, - Graeme - _______________________________________________ fpGUI - a cross-platform Free Pascal GUI toolkit http://opensoft.homeip.net/fpgui/
Michael Van Canneyt
to Graeme Geldenhuys: Sorry, I didn't mean for storing at the property level, I meant a public boolean function…
Graeme Geldenhuys wrote 7 lines
To get back to the point. So must I now override GetChildren for every single component in fpGUI that can contain child components and do the loopy Components[] array thing? Or do I only need to override GetChildren in TfpgBaseForm - the top level window/container of all other components? BTW: In fpGUI TfpgForm can act as both a TForm and TFrame.

It is up to you. Both approaches are possible. Like I said, in the LCL it is TWinControl that does the loop over it's child controls. But you could equally well let the form loop over all components that are directly or indirectly on it.

See, maybe it's not such a botched idea after all ? You get a lot of freedom :-)

Michael.

Graeme Geldenhuys
to Michael Van Canneyt

Op 2010-06-04 08:41, Michael Van Canneyt het geskryf:

It is up to you. Both approaches are possible. Like I said, in the LCL it is TWinControl that does the loop over it's child controls. But you could

LCL implements GetChildren in TWinControl, TFrame and TCustomForm. But looking more closely at the code, TWinControl loops over Controls property and TCustomForm loops over Components property.

I now understand why Martin implemented MSEgui from the ground up and not basing any code on Borland's ideas. You never need to fight strange implementations, other than your own. :)

Regards,
- Graeme -

fpGUI Toolkit - a cross-platform GUI toolkit using Free Pascal http://opensoft.homeip.net/fpgui/
Mattias Gaertner
to Graeme Geldenhuys: I can't think that's the reason. D1 thru D5 (or D7) saved dfm files in binary format. So…
Show quoted text 10 lines
On 3 June 2010 22:55, Mattias Gaertner wrote: > I other words: There are two tree like structures: Owner and Parent. > Parent is optional and will make your stream files more readable. > >[...] > > Readability. I can't think that's the reason. D1 thru D5 (or D7) saved dfm files in binary format. So there was no readability to speak of.

Afaik the you had always the possibility to edit the form as text format. The binary format had a few advantages when saving to disk.

Also the only people that actually look (and care) about the dfm/lfm files are the IDE designers. Normal developers almost never look inside those files (unless something major screwed up - in which case they should have made backups).

Almost never is not never. I guess almost every developer has looked it it.

There is also another reason:
The parent property is set before all other. This is important to reduce overhead, especially on the early windows versions.

[...]

Mattias

Martin Schreiber
to Graeme Geldenhuys: Op 2010-06-04 08:41, Michael Van Canneyt het geskryf: LCL implements GetChildren in…
Graeme Geldenhuys wrote 5 lines
I now understand why Martin implemented MSEgui from the ground up and not basing any code on Borland's ideas. You never need to fight strange implementations, other than your own. :)

Although you are right in principle, owner/parent relationship is a bad example because MSEgui uses the default streaming mechanism. :-) I wanted to be able to use forign non visual components in MSEgui and I think the concept is not so bad because there is more flexibility.

Martin