Recent

Author Topic: Streaming components with LResources unit  (Read 3438 times)

lainz

  • Hero Member
  • *****
  • Posts: 4468
    • https://lainz.github.io/
Streaming components with LResources unit
« on: March 23, 2014, 08:21:13 pm »
HI, this is the unit I have.

A component Demo2 that owns component Demo1.
Demo2 props are saved fine, but Demo1 aren't.

Isn't supossed that all children will be stored too?

this is the output demo.txt, as you can see, the Demo1 name is stored, but not the other props.
Code: [Select]
object i_m_the_demo_2: TDemo2
  Demo1 = demo_1_inside_demo_2
  SomeText = 'i_m_sometext'
end

This is the unit.
Code: [Select]
unit udemo;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, LResources;

type

  { TDemo1 }

  TDemo1 = class(TComponent)
  private
    FID: integer;
    FStr: string;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property ID: integer read FID write FID;
    property Str: string read FStr write FStr;
  end;

  { TDemo2 }

  TDemo2 = class(TComponent)
  private
    FDemo1: TDemo1;
    FSomeText: string;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure SaveToTextFile(FileName: string);
  published
    property Demo1: TDemo1 read FDemo1 write FDemo1;
    property SomeText: string read FSomeText write FSomeText;
  end;

implementation

var
  demo2: TDemo2;

{ TDemo1 }

constructor TDemo1.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
end;

destructor TDemo1.Destroy;
begin
  inherited Destroy;
end;

{ TDemo2 }

constructor TDemo2.Create(AOwner: TComponent);
begin
  Demo1 := TDemo1.Create(Self);
  inherited Create(AOwner);
end;

destructor TDemo2.Destroy;
begin
  inherited Destroy;
end;

procedure TDemo2.SaveToTextFile(FileName: string);
var
  AStream: TMemoryStream;
begin
  AStream := TMemoryStream.Create;
  try
    WriteComponentAsTextToStream(AStream, Self);
    AStream.SaveToFile(FileName);
  finally
    AStream.Free;
  end;
end;

initialization
  demo2 := TDemo2.Create(nil);
  // Demo2 props
  demo2.Name:='i_m_the_demo_2';
  demo2.SomeText:='i_m_sometext';
  // Demo1 props
  demo2.Demo1.Name:='demo_1_inside_demo_2';
  demo2.Demo1.ID:=123;
  demo2.Demo1.Str:='hello_from_demo1_inside_demo_2';
  // write file
  demo2.SaveToTextFile('demo.txt');

finalization
  demo2.Free;

end.

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: Streaming components with LResources unit
« Reply #1 on: March 23, 2014, 09:58:05 pm »
AFAIK (happy to be proved wrong) WriteComponentAsTextToStream() does not have the power to recognise that a TComponent property will contain subproperties, unfurl those class properties and write such class property data to the stream. The only property it sees is the component name.

You have to add this capability yourself by overriding TPersistent's virtual DefineProperties() method, and add appropriate TReaderProc and TWriterProc methods (where you see they are needed) to stream the TComponent property's 'subproperties'. In your case you can leave the Demo1 class and the initialization block unchanged. You have to extend the Demo2 class as follows:

Code: [Select]

 ...
  TDemo2 = class(TComponent)
  private
    FDemo1: TDemo1;
    FSomeText: string;
    procedure ReadInt(Reader: TReader);
    procedure WriteInt(Writer: TWriter);
    procedure ReadStr(Reader: TReader);
    procedure WriteStr(Writer: TWriter);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure SaveToTextFile(FileName: string);
    procedure DefineProperties(Filer: TFiler); override;
  published
    property Demo1: TDemo1 read FDemo1 write FDemo1;
    property SomeText: string read FSomeText write FSomeText;
  end;

implementation

var
  demo2: TDemo2;


{ TDemo2 }

procedure TDemo2.ReadInt(Reader: TReader);
begin
  Demo1.ID:=Reader.ReadInteger;
end;

procedure TDemo2.WriteInt(Writer: TWriter);
begin
  Writer.WriteInteger(Demo1.ID);
end;

procedure TDemo2.ReadStr(Reader: TReader);
begin
  Demo1.Str:=Reader.ReadString;
end;

procedure TDemo2.WriteStr(Writer: TWriter);
begin
  Writer.WriteString(Demo1.Str);
end;

constructor TDemo2.Create(AOwner: TComponent);
begin
  FDemo1 := TDemo1.Create(Self);
  inherited Create(AOwner);
end;

destructor TDemo2.Destroy;
begin
  inherited Destroy;
end;

procedure TDemo2.SaveToTextFile(FileName: string);
var
  AStream: TMemoryStream;
begin
  AStream := TMemoryStream.Create;
  try
    WriteComponentAsTextToStream(AStream, Self);
    AStream.SaveToFile(FileName);
  finally
    AStream.Free;
  end;
end;

procedure TDemo2.DefineProperties(Filer: TFiler);
begin
  Filer.DefineProperty('Demo1.ID', @ReadInt, @WriteInt, True);
  Filer.DefineProperty('Demo1.Str', @ReadStr, @WriteStr, (Demo1.Str<>''));
end;

 ...

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: Streaming components with LResources unit
« Reply #2 on: March 24, 2014, 12:00:41 am »
AFAIK (happy to be proved wrong) ...
Howard, with the intention of making you happy.  According to the source code:
Code: [Select]
procedure TWriter.WriteProperty(Instance: TPersistent; PropInfo: Pointer);
...
  // properties without setter are only allowed, if they are subcomponents
  PropType := PPropInfo(PropInfo)^.PropType;
  if not Assigned(PPropInfo(PropInfo)^.SetProc) then begin
    if PropType^.Kind<>tkClass then
      exit;
    ObjValue := TObject(GetObjectProp(Instance, PropInfo));
    if not ObjValue.InheritsFrom(TComponent) or
       not (csSubComponent in TComponent(ObjValue).ComponentStyle) then
      exit;
...
  case PropType^.Kind of
...
    tkClass:
...
Making Demo1 a sub component in its constructor:
Code: [Select]
constructor TDemo1.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  SetSubComponent(True);//<---
end;

or making one instance of Demo1 a sub component in the constructor of Demo2:
Code: [Select]
constructor TDemo2.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Demo1 := TDemo1.Create(Self);
  Demo1.SetSubComponent(True);//<---
end;

should produce the following text file for the OP's example:
Quote
object demo_1_inside_demo_2: TDemo2
  Demo1.ID = 123
  Demo1.Str = 'hello_from_demo1_inside_demo_2'
  SomeText = 'i_m_sometext'
end

lainz

  • Hero Member
  • *****
  • Posts: 4468
    • https://lainz.github.io/
Re: Streaming components with LResources unit
« Reply #3 on: March 24, 2014, 02:32:15 am »
Thanks! Both are useful!

The SetSubComponent does the trick.

The first example is useful to stream custom data.

Edit: I've added a wiki article:
http://wiki.lazarus.freepascal.org/TCollection#Streaming
« Last Edit: March 26, 2014, 09:45:54 pm by 007 »

 

TinyPortal © 2005-2018