Recent

Author Topic: Writing And Reading a Components To From a Stream  (Read 816 times)

LBoxPO

  • New Member
  • *
  • Posts: 15
Writing And Reading a Components To From a Stream
« on: September 05, 2024, 12:36:10 am »
Hi everyone )

Here is a good example of how to write components to a stream and read them by recreating them on a form.
https://wiki.freepascal.org/Streaming_components
C:\lazarus\examples\componentstreaming

The problem is that the example involves only one object "AGroupBox" with one child object "ACheckBox", and studying the code I do not quite understand how to make all the components on the form be written to the stream, because in most programs the form will have many more components and different classes. Moreover, when I added the child component "TButton" to "AGroupBox" and changed

this code:
procedure TCompStreamDemoForm.OnFindClass(Reader: TReader;
const AClassName: string; var ComponentClass: TComponentClass);
begin
  if CompareText(AClassName,'TGroupBox')=0 then
    ComponentClass:=TGroupBox
  else if CompareText(AClassName,'TCheckBox')=0 then
    ComponentClass:=TCheckBox;
end;

to this:
procedure TCompStreamDemoForm.OnFindClass(Reader: TReader;
const AClassName: string; var ComponentClass: TComponentClass);
begin
  if CompareText(AClassName,'TGroupBox')=0 then
    ComponentClass:=TGroupBox
  else if CompareText(AClassName,'TCheckBox')=0 then
    ComponentClass:=TCheckBox
  else if CompareText(AClassName,'TButton')=0 then
    ComponentClass:=TButton;
end;

the "TButton" component I added to the stream was not recorded, that is, I was unable to add information about the second child object to the stream and only information about "AGroupBox" and its child object ACheckBox was recorded in the stream.

Please, Pascal experts, explain to me what I am doing wrong and what needs to be done so that any number of components with any number of child objects can be recorded in the stream and then recreated on the form?

As I understand it, a cycle will definitely be required here like this

// Conditional code
procedure WriteAllComponentsToStream(...)
var
  i: Integer;
  AStream: TMemoryStream; // Or TFileStream
begin
  for i := 0 to TForm.ComponentsCount - 1 do
  begin
    AppendWriteComponentToStream(AStream, TForm.Component);
  end;
end;
« Last Edit: September 05, 2024, 12:41:38 am by LBoxPO »

ASerge

  • Hero Member
  • *****
  • Posts: 2337
Re: Writing And Reading a Components To From a Stream
« Reply #1 on: September 05, 2024, 09:20:39 am »
1. Use code tags for source.
2. The tools for reading and writing components uses a hierarchy through the owner, not the parent.

LBoxPO

  • New Member
  • *
  • Posts: 15
Re: Writing And Reading a Components To From a Stream
« Reply #2 on: September 05, 2024, 11:35:06 am »
1. Use code tags for source.
2. The tools for reading and writing components uses a hierarchy through the owner, not the parent.

Thank you very much, it worked  8)
Your advice about owners helped and while studying this topic
I realized that it is impossible to assign an owner by writing
like this:
Button1.Owner := AGroupBox;

need to write like this
AGroupBox.InsertComponent(Button1);

As you can see in the picture, now everything is written and recreated correctly, but now an error occurs when closing the form  :(

I also did not quite understand your first advice about "Use code tags for source".
Can you give a link to examples on the topic of code tags?


Zvoni

  • Hero Member
  • *****
  • Posts: 2741
Re: Writing And Reading a Components To From a Stream
« Reply #3 on: September 05, 2024, 11:40:40 am »
I also did not quite understand your first advice about "Use code tags for source".
Can you give a link to examples on the topic of code tags?

Code-Tags:
(remove the blank between [ and 'c')
[ code=pascal]
Some Pascal code
[/ code]
(remove the blank between / and 'c')
One System to rule them all, One Code to find them,
One IDE to bring them all, and to the Framework bind them,
in the Land of Redmond, where the Windows lie
---------------------------------------------------------------------
Code is like a joke: If you have to explain it, it's bad

ASerge

  • Hero Member
  • *****
  • Posts: 2337
Re: Writing And Reading a Components To From a Stream
« Reply #4 on: September 05, 2024, 11:58:18 am »
As you can see in the picture, now everything is written and recreated correctly, but now an error occurs when closing the form  :(
We need the full source code to see where the error is.

LBoxPO

  • New Member
  • *
  • Posts: 15
Re: Writing And Reading a Components To From a Stream
« Reply #5 on: September 05, 2024, 02:03:30 pm »
We need the full source code to see where the error is.

Below is the entire code of the mainunit.pas file, and just in case I will attach the project folder with all the project files.

Code: Pascal  [Select][+][-]
  1. unit MainUnit;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, LCLProc, LResources, Forms, Controls, Graphics,
  9.   Dialogs, StdCtrls, Buttons;
  10.  
  11. type
  12.  
  13.   { TCompStreamDemoForm }
  14.  
  15.   TCompStreamDemoForm = class(TForm)
  16.     AGroupBox: TGroupBox;
  17.     Button1: TButton;
  18.     StreamAsLFMCheckBox: TCheckBox;
  19.     Note2Label: TLabel;
  20.     Note1Label: TLabel;
  21.     ReadStreamButton: TButton;
  22.     StreamMemo: TMemo;
  23.     StreamGroupBox: TGroupBox;
  24.     WriteToStreamButton: TButton;
  25.     SourceGroupBox: TGroupBox;
  26.     DestinationGroupBox: TGroupBox;
  27.     procedure FormCreate(Sender: TObject);
  28.     procedure ReadStreamButtonClick(Sender: TObject);
  29.     procedure StreamAsLFMCheckBoxChange(Sender: TObject);
  30.     procedure WriteToStreamButtonClick(Sender: TObject);
  31.   public
  32.     StreamAsString: string;
  33.     procedure ShowStreamInMemo;
  34.     procedure SaveStreamAsString(AStream: TStream);
  35.     procedure ReadStreamFromString(AStream: TStream);
  36.     function ReadStringFromStream(AStream: TStream): string;
  37.     procedure ClearDestinationGroupBox;
  38.     procedure OnFindClass(Reader: TReader; const AClassName: string; var ComponentClass: TComponentClass);
  39.   end;
  40.  
  41. var
  42.   CompStreamDemoForm: TCompStreamDemoForm;
  43.   i: Integer;
  44.  
  45. implementation
  46.  
  47. {$R *.lfm}
  48.  
  49. { TCompStreamDemoForm }
  50.  
  51. procedure TCompStreamDemoForm.WriteToStreamButtonClick(Sender: TObject);
  52. var
  53.   AStream: TMemoryStream;
  54. begin
  55.   AStream:=TMemoryStream.Create;
  56.   try
  57.     WriteComponentAsBinaryToStream(AStream, AGroupBox);
  58.     SaveStreamAsString(AStream);
  59.   finally
  60.     AStream.Free;
  61.   end;
  62. end;
  63.  
  64. procedure TCompStreamDemoForm.ReadStreamButtonClick(Sender: TObject);
  65. var
  66.   NewComponent: TComponent;
  67.   AStream: TMemoryStream;
  68. begin
  69.   ClearDestinationGroupBox;
  70.  
  71.   AStream:=TMemoryStream.Create;
  72.   try
  73.     ReadStreamFromString(AStream);
  74.     NewComponent:=nil;
  75.     ReadComponentFromBinaryStream(AStream,NewComponent, @OnFindClass,DestinationGroupBox);
  76.     if NewComponent is TControl then
  77.       TControl(NewComponent).Parent:=DestinationGroupBox;
  78.   finally
  79.     AStream.Free;
  80.   end;
  81. end;
  82.  
  83. procedure TCompStreamDemoForm.FormCreate(Sender: TObject);
  84. var
  85.   ACheckBox: TCheckBox;
  86. begin
  87.   // create a checkbox with Owner = AGroupBox
  88.   // because TWriter writes all components owned by AGroupBox
  89.   ACheckBox:=TCheckBox.Create(AGroupBox);
  90.   with ACheckBox do begin
  91.     Name:='ACheckBox';
  92.     Parent:=AGroupBox;
  93.   end;
  94.  
  95.   //Button1.Owner := AGroupBox;  // Not Works
  96.   AGroupBox.InsertComponent(Button1); // Works
  97.  
  98.   //showmessage ('AGroupBox Child Count = ' + IntToStr(AGroupBox.ControlCount));
  99.   //showmessage ('Button1 Owner = ' + Button1.Owner.Name);
  100.   //showmessage ('Button1 Parent = ' + Button1.Parent.Name);
  101. end;
  102.  
  103. procedure TCompStreamDemoForm.StreamAsLFMCheckBoxChange(Sender: TObject);
  104. begin
  105.   ShowStreamInMemo;
  106. end;
  107.  
  108. procedure TCompStreamDemoForm.ShowStreamInMemo;
  109. var
  110.   LRSStream: TMemoryStream;
  111.   LFMStream: TMemoryStream;
  112. begin
  113.   if StreamAsLFMCheckBox.Checked then begin
  114.     // convert the stream to LFM
  115.     LRSStream:=TMemoryStream.Create;
  116.     LFMStream:=TMemoryStream.Create;
  117.     try
  118.       ReadStreamFromString(LRSStream);
  119.       LRSObjectBinaryToText(LRSStream,LFMStream);
  120.       StreamMemo.Lines.Text:=ReadStringFromStream(LFMStream);
  121.     finally
  122.       LRSStream.Free;
  123.       LFMStream.Free;
  124.     end;
  125.   end else begin
  126.     // the stream is in binary format and contains characters, that can not be
  127.     // shown in the memo. Convert all special characters to hexnumbers.
  128.     StreamMemo.Lines.Text:=DbgStr(StreamAsString);
  129.   end;
  130. end;
  131.  
  132. procedure TCompStreamDemoForm.SaveStreamAsString(AStream: TStream);
  133. begin
  134.   StreamAsString:=ReadStringFromStream(AStream);
  135.   ShowStreamInMemo;
  136. end;
  137.  
  138. procedure TCompStreamDemoForm.ReadStreamFromString(AStream: TStream);
  139. begin
  140.   AStream.Size:=0;
  141.   if StreamAsString<>'' then
  142.     AStream.Write(StreamAsString[1],length(StreamAsString));
  143.   AStream.Position:=0;
  144. end;
  145.  
  146. function TCompStreamDemoForm.ReadStringFromStream(AStream: TStream): string;
  147. begin
  148.   AStream.Position:=0;
  149.   SetLength(Result,AStream.Size);
  150.   if Result<>'' then
  151.     AStream.Read(Result[1],length(Result));
  152. end;
  153.  
  154. procedure TCompStreamDemoForm.ClearDestinationGroupBox;
  155. { free all components owned by DestinationGroupBox
  156.   Do not confuse 'Owner' and 'Parent';
  157.   The 'Owner' of a TComponent is responsible for freeing the component.
  158.   All components owned by a component can be found in its 'Components'
  159.   property.
  160.   The 'Parent' of a TControl is the visible container. For example
  161.   DestinationGroupBox has as Parent the form (CompStreamDemoForm).
  162.   All controls with the same parent are gathered in Parent.Controls.
  163.  
  164.   In this simple example the created component has as Owner and Parent the
  165.   DestinationGroupBox.
  166. }
  167. begin
  168.   while DestinationGroupBox.ComponentCount>0 do
  169.     DestinationGroupBox.Components[0].Free;
  170. end;
  171.  
  172. procedure TCompStreamDemoForm.OnFindClass(Reader: TReader; const AClassName: string; var ComponentClass: TComponentClass);
  173. begin
  174.   if CompareText(AClassName,'TGroupBox')=0 then
  175.     ComponentClass:=TGroupBox
  176.   else if CompareText(AClassName,'TButton')=0 then
  177.     ComponentClass:=TButton
  178.   else if CompareText(AClassName,'TCheckBox')=0 then
  179.     ComponentClass:=TCheckBox;
  180. end;
  181.  
  182. end.
  183.  
« Last Edit: September 05, 2024, 07:32:41 pm by LBoxPO »

LBoxPO

  • New Member
  • *
  • Posts: 15
Re: Writing And Reading a Components To From a Stream
« Reply #6 on: September 05, 2024, 02:54:47 pm »
Code-Tags:
(remove the blank between [ and 'c')
[ code=pascal]
Some Pascal code
[/ code]
(remove the blank between / and 'c')

The instructions are clear, but the search shows that there is no " [ " symbol in the code,
and the " / " symbol is used only before comments, where, as I understand it, a space is not a problem.

dseligo

  • Hero Member
  • *****
  • Posts: 1408
Re: Writing And Reading a Components To From a Stream
« Reply #7 on: September 05, 2024, 05:36:38 pm »
Code-Tags:
(remove the blank between [ and 'c')
[ code=pascal]
Some Pascal code
[/ code]
(remove the blank between / and 'c')

The instructions are clear, but the search shows that there is no " [ " symbol in the code,
and the " / " symbol is used only before comments, where, as I understand it, a space is not a problem.

Those were instructions how to put your code here in forum, not correct error in your code.

I.e., like this:

Code: Pascal  [Select][+][-]
  1. unit MainUnit;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, LCLProc, LResources, Forms, Controls, Graphics,
  9.   Dialogs, StdCtrls, Buttons;
  10.  
  11. type
  12.  
  13.   { TCompStreamDemoForm }
  14.  
  15.   TCompStreamDemoForm = class(TForm)
  16.     AGroupBox: TGroupBox;
  17.     Button1: TButton;
  18.     StreamAsLFMCheckBox: TCheckBox;
  19.     Note2Label: TLabel;
  20.     Note1Label: TLabel;
  21.     ReadStreamButton: TButton;
  22.     StreamMemo: TMemo;
  23.     StreamGroupBox: TGroupBox;
  24.     WriteToStreamButton: TButton;
  25.     SourceGroupBox: TGroupBox;
  26.     DestinationGroupBox: TGroupBox;
  27.     procedure FormCreate(Sender: TObject);
  28.     procedure ReadStreamButtonClick(Sender: TObject);
  29.     procedure StreamAsLFMCheckBoxChange(Sender: TObject);
  30.     procedure WriteToStreamButtonClick(Sender: TObject);
  31.   public
  32.     StreamAsString: string;
  33.     procedure ShowStreamInMemo;
  34.     procedure SaveStreamAsString(AStream: TStream);
  35.     procedure ReadStreamFromString(AStream: TStream);
  36.     function ReadStringFromStream(AStream: TStream): string;
  37.     procedure ClearDestinationGroupBox;
  38.     procedure OnFindClass(Reader: TReader; const AClassName: string; var ComponentClass: TComponentClass);
  39.   end;
  40.  
  41. var
  42.   CompStreamDemoForm: TCompStreamDemoForm;
  43.   i: Integer;
  44.  
  45. implementation
  46.  
  47. {$R *.lfm}
  48.  
  49. { TCompStreamDemoForm }
  50.  
  51. procedure TCompStreamDemoForm.WriteToStreamButtonClick(Sender: TObject);
  52. var
  53.   AStream: TMemoryStream;
  54. begin
  55.   AStream:=TMemoryStream.Create;
  56.   try
  57.     WriteComponentAsBinaryToStream(AStream, AGroupBox);
  58.     SaveStreamAsString(AStream);
  59.   finally
  60.     AStream.Free;
  61.   end;
  62. end;
  63.  
  64. procedure TCompStreamDemoForm.ReadStreamButtonClick(Sender: TObject);
  65. var
  66.   NewComponent: TComponent;
  67.   AStream: TMemoryStream;
  68. begin
  69.   ClearDestinationGroupBox;
  70.  
  71.   AStream:=TMemoryStream.Create;
  72.   try
  73.     ReadStreamFromString(AStream);
  74.     NewComponent:=nil;
  75.     ReadComponentFromBinaryStream(AStream,NewComponent, @OnFindClass,DestinationGroupBox);
  76.     if NewComponent is TControl then
  77.       TControl(NewComponent).Parent:=DestinationGroupBox;
  78.   finally
  79.     AStream.Free;
  80.   end;
  81. end;
  82.  
  83. procedure TCompStreamDemoForm.FormCreate(Sender: TObject);
  84. var
  85.   ACheckBox: TCheckBox;
  86. begin
  87.   // create a checkbox with Owner = AGroupBox
  88.   // because TWriter writes all components owned by AGroupBox
  89.   ACheckBox:=TCheckBox.Create(AGroupBox);
  90.   with ACheckBox do begin
  91.     Name:='ACheckBox';
  92.     Parent:=AGroupBox;
  93.   end;
  94.  
  95.   //Button1.Owner := AGroupBox;  // Not Works
  96.   AGroupBox.InsertComponent(Button1); // Works
  97.  
  98.   //showmessage ('AGroupBox Child Count = ' + IntToStr(AGroupBox.ControlCount));
  99.   //showmessage ('Button1 Owner = ' + Button1.Owner.Name);
  100.   //showmessage ('Button1 Parent = ' + Button1.Parent.Name);
  101. end;
  102.  
  103. procedure TCompStreamDemoForm.StreamAsLFMCheckBoxChange(Sender: TObject);
  104. begin
  105.   ShowStreamInMemo;
  106. end;
  107.  
  108. procedure TCompStreamDemoForm.ShowStreamInMemo;
  109. var
  110.   LRSStream: TMemoryStream;
  111.   LFMStream: TMemoryStream;
  112. begin
  113.   if StreamAsLFMCheckBox.Checked then begin
  114.     // convert the stream to LFM
  115.     LRSStream:=TMemoryStream.Create;
  116.     LFMStream:=TMemoryStream.Create;
  117.     try
  118.       ReadStreamFromString(LRSStream);
  119.       LRSObjectBinaryToText(LRSStream,LFMStream);
  120.       StreamMemo.Lines.Text:=ReadStringFromStream(LFMStream);
  121.     finally
  122.       LRSStream.Free;
  123.       LFMStream.Free;
  124.     end;
  125.   end else begin
  126.     // the stream is in binary format and contains characters, that can not be
  127.     // shown in the memo. Convert all special characters to hexnumbers.
  128.     StreamMemo.Lines.Text:=DbgStr(StreamAsString);
  129.   end;
  130. end;
  131.  
  132. procedure TCompStreamDemoForm.SaveStreamAsString(AStream: TStream);
  133. begin
  134.   StreamAsString:=ReadStringFromStream(AStream);
  135.   ShowStreamInMemo;
  136. end;
  137.  
  138. procedure TCompStreamDemoForm.ReadStreamFromString(AStream: TStream);
  139. begin
  140.   AStream.Size:=0;
  141.   if StreamAsString<>'' then
  142.     AStream.Write(StreamAsString[1],length(StreamAsString));
  143.   AStream.Position:=0;
  144. end;
  145.  
  146. function TCompStreamDemoForm.ReadStringFromStream(AStream: TStream): string;
  147. begin
  148.   AStream.Position:=0;
  149.   SetLength(Result,AStream.Size);
  150.   if Result<>'' then
  151.     AStream.Read(Result[1],length(Result));
  152. end;
  153.  
  154. procedure TCompStreamDemoForm.ClearDestinationGroupBox;
  155. { free all components owned by DestinationGroupBox
  156.   Do not confuse 'Owner' and 'Parent';
  157.   The 'Owner' of a TComponent is responsible for freeing the component.
  158.   All components owned by a component can be found in its 'Components'
  159.   property.
  160.   The 'Parent' of a TControl is the visible container. For example
  161.   DestinationGroupBox has as Parent the form (CompStreamDemoForm).
  162.   All controls with the same parent are gathered in Parent.Controls.
  163.  
  164.   In this simple example the created component has as Owner and Parent the
  165.   DestinationGroupBox.
  166. }
  167. begin
  168.   while DestinationGroupBox.ComponentCount>0 do
  169.     DestinationGroupBox.Components[0].Free;
  170. end;
  171.  
  172. procedure TCompStreamDemoForm.OnFindClass(Reader: TReader; const AClassName: string; var ComponentClass: TComponentClass);
  173. begin
  174.   if CompareText(AClassName,'TGroupBox')=0 then
  175.     ComponentClass:=TGroupBox
  176.   else if CompareText(AClassName,'TButton')=0 then
  177.     ComponentClass:=TButton
  178.   else if CompareText(AClassName,'TCheckBox')=0 then
  179.     ComponentClass:=TCheckBox;
  180. end;
  181.  
  182. end.

LBoxPO

  • New Member
  • *
  • Posts: 15
Re: Writing And Reading a Components To From a Stream
« Reply #8 on: September 05, 2024, 07:35:51 pm »
Those were instructions how to put your code here in forum, not correct error in your code.

Thanks for the advice, now I understand  :D

ASerge

  • Hero Member
  • *****
  • Posts: 2337
Re: Writing And Reading a Components To From a Stream
« Reply #9 on: September 06, 2024, 05:44:49 am »
We need the full source code to see where the error is.
Below is the entire code of the mainunit.pas file, and just in case I will attach the project folder with all the project files.
Double free of the Button1. It's owned by TCompStreamDemoForm and AGroupBox. Remove it from TCompStreamDemoForm:
Code: Pascal  [Select][+][-]
  1. procedure TCompStreamDemoForm.FormCreate(Sender: TObject);
  2. var
  3.   ACheckBox: TCheckBox;
  4.   SaveRef: TComponent;
  5. begin
  6. //...
  7.   //Button1.Owner := AGroupBox;  // Not Works
  8.   SaveRef := Button1;
  9.   RemoveComponent(Button1);
  10.   AGroupBox.InsertComponent(SaveRef); // Works
  11. //...

By the way. There is TStringStream. And DestroyComponents. There is no need to reinvent the wheel ;)

LBoxPO

  • New Member
  • *
  • Posts: 15
Re: Writing And Reading a Components To From a Stream
« Reply #10 on: September 06, 2024, 01:46:21 pm »
We need the full source code to see where the error is.
Below is the entire code of the mainunit.pas file, and just in case I will attach the project folder with all the project files.
Double free of the Button1. It's owned by TCompStreamDemoForm and AGroupBox. Remove it from TCompStreamDemoForm:
Code: Pascal  [Select][+][-]
  1. procedure TCompStreamDemoForm.FormCreate(Sender: TObject);
  2. var
  3.   ACheckBox: TCheckBox;
  4.   SaveRef: TComponent;
  5. begin
  6. //...
  7.   //Button1.Owner := AGroupBox;  // Not Works
  8.   SaveRef := Button1;
  9.   RemoveComponent(Button1);
  10.   AGroupBox.InsertComponent(SaveRef); // Works
  11. //...
By the way. There is TStringStream. And DestroyComponents. There is no need to reinvent the wheel :)

Wow, super! 😃 Thank you very much 👍 Now the program closes without errors  :)

I will definitely study the topic "TStringStream" and "DestroyComponents", but in general
I am interested in the method of saving and loading the entire user interface.

I found a similar topic on the forum
https://forum.lazarus.freepascal.org/index.php/topic,36241.msg241182.html#msg241182
and the method proposed there seems to solve this problem, but I have not been able to repeat it yet,
because most likely I am incorrectly applying the procedures proposed there.

The original source of the proposed solution is here.
https://stackoverflow.com/questions/3163586/how-to-save-and-restore-a-form

In the near future, I will write in this topic
https://forum.lazarus.freepascal.org/index.php/topic,36241.msg241182.html#msg241182
with a demonstration of the error that I get when trying to use the methods proposed there.

I will be glad to any advice that will help me find and understand the best solutions for saving and loading a dynamic user interface.
If we take it completely globally, then I am interested in how the extensions of project files are created,
which ultimately allow the end user to save and load the work they have done in a particular program.
« Last Edit: September 06, 2024, 01:48:42 pm by LBoxPO »

 

TinyPortal © 2005-2018