Eros
Eros

Reputation: 469

Delphi stream panel to file

today I've a question about streaming a part of a form to a file. In this example i use a Tmemo instead of file in order to see the stream.

here is my form:

enter image description here

The panel on the right top of the form has some controls, like label, edit and so on. with the "Save panel" butto I save the panel on a TStream:

Here the code:

procedure TfrmMain.btnSaveClick(Sender: TObject);
var
  idx: Integer;
  MemStr: TStream;
begin
  MemStr := TMemoryStream.Create;
  PanelStr := TMemoryStream.Create;
  try
    for idx := 0 to pnlSource.ControlCount - 1 do begin
      MemStr.Position := 0;
      MemStr.WriteComponent(pnlSource.Controls[idx]);
      StreamConvert(MemStr);
    end;
    PanelStr.Position := 0;
    mmoStream.Lines.LoadFromStream(PanelStr);
  finally
    MemStr.Free;
  end;
end;

and here the StreamConvert:

{ Conversione stream in formato testo }
procedure TfrmMain.StreamConvert(aStream: TStream);
var
  ConvStream: TStream;
begin
  aStream.Position := 0;
  ConvStream := TMemoryStream.Create;
  try
    ObjectBinaryToText(aStream, ConvStream);
    ConvStream.Position := 0;
    PanelStr.CopyFrom(ConvStream, ConvStream.Size);
    lblStreamSize.Caption := IntToStr(ConvStream.Size);
  finally
    ConvStream.Free;
  end;
end;

PanelStr is a TStream object declared in private section of the form and create during form create. This part works good and, as you can see in right part of the image the elements present on the form are register correctly.

Now my problem is to restore this element into the panel on the left bottom of the form. I've tryed this routine:

{ Carica i controlli presenti nel pannello pnlSource in uno stream }
procedure TfrmMain.btnLoadClick(Sender: TObject);
var
  idx:  Integer;
  MemStr: TStream;
begin
  pnlSource.Free;
  MemStr := TMemoryStream.Create;
  try
    PanelStr.Position := 0;
    ObjectTextToBinary(PanelStr, MemStr);
    MemStr.Position := 0;
    MemStr.ReadComponent(pnlTarget);
  finally
    MemStr.Free;
  end;
end;

but it doesn't work and in the following picture you can see the result:

enter image description here

What is wrong in my routine, and How can I read all the element present in the stream and not only the first?

Can someone help me in this headache?

Upvotes: 3

Views: 1272

Answers (2)

Dsm
Dsm

Reputation: 6013

As I put in my comments, you need to surround your data with Panel2 information. You also need to register each control type you are saving and restoring.

This means that only the load procedure needs to change - like this:

procedure TfrmMain.btnLoadClick(Sender: TObject);
var
  iTemp, iTemp2 : TStringList;
  MemStr: TStream;
  i: Integer;
begin
  // first read the destination panel an put it into a string list
  pnlSource.Free;
  iTemp := TStringList.Create;
  iTemp2 := TStringList.Create;
  iTemp.Duplicates := TDuplicates.dupAccept;
  iTemp2.Duplicates := TDuplicates.dupAccept;
  MemStr := TMemoryStream.Create;
  try
    PanelStr.Position := 0;
    iTemp2.LoadFromStream( PanelStr ); // our original source
    PanelStr.Size := 0;
    MemStr.Position := 0;
    MemStr.WriteComponent(pnlTarget);
    StreamConvert(MemStr);
    // PanelStr now has our destination poanel.
    PanelStr.Position := 0;
    iTemp.LoadFromStream( PanelStr );
    for i := 0 to iTemp2.Count - 1 do
    begin
      iTemp.Insert( ITemp.Count - 1, iTemp2[ i ]);
    end;
    PanelStr.Size := 0;
    iTemp.SaveToStream( PanelStr );
    PanelStr.Position := 0;
    mmoStream.Lines.LoadFromStream(PanelStr);
    MemStr.Size := 0;
    PanelStr.Position := 0;
    ObjectTextToBinary( PanelStr, MemStr);
    MemStr.Position := 0;
    RegisterClass( TLabel );
    RegisterClass( TPanel );
    RegisterClass( TEdit );
    RegisterClass( TCheckBox );
    RegisterClass( TRadioButton );
    MemStr.ReadComponent( pnlTarget );

  finally
    iTemp.Free;
    iTemp2.Free;
    MemStr.Free;
  end;
end;

As commented in the previous answer, registration can be put somewhere else.

Unlike the previous answer, you do not need to change the ownership of the controls first. (That is just a comment - not a criticism). This is just an implementation of my comment.

My naming conventions are different to yours. I have tried to use the same names, but forgive me if I have missed any.

Upvotes: 0

Sertac Akyuz
Sertac Akyuz

Reputation: 54772

The code you are currently running effectively transforms the source panel to a label. That's because the first object streamed is a label and the code is reading only one component. IOW, when the reader reaches the first end, reading is complete since there are no sub controls in the stream.

So, first of all, you have to write the panel - and only the panel. The panel is the one that is supposed to stream it's children. To have it to do so, it must own it's controls.

var
  idx: Integer;
  MemStr: TStream;
begin
  MemStr := TMemoryStream.Create;
  PanelStr := TMemoryStream.Create;
  try
    // transfer ownership of controls to the panel
    for idx := 0 to pnlSource.ControlCount - 1 do
      pnlSource.InsertComponent(pnlSource.Controls[idx]);
    // write the panel
    MemStr.WriteComponent(pnlSource);

    StreamConvert(MemStr);
    PanelStr.Position := 0;
    mmoStream.Lines.LoadFromStream(PanelStr);
  finally
    MemStr.Free;
  end;

This produces an output to the memo like this:

object pnlSource: TPanel
  Left = 8
  Top = 8
  Width = 201
  Height = 265
  Caption = 'pnlSource'
  TabOrder = 0
  object Label1: TLabel
    Left = 48
    Top = 208
    Width = 31
    Height = 13
    Caption = 'Label1'
  end
  object Label2: TLabel
    ...

Note the indentation of the label definition and the missing 'end' of the owning panel (it's at the end).

You will need to register classes for the streamer to be able to find them when loading:

var
  idx:  Integer;
  MemStr: TStream;
begin
  pnlSource.Free;

  RegisterClasses([TLabel, TEdit, TCheckBox, TRadioButton]);

  MemStr := TMemoryStream.Create;
  try
    PanelStr.Position := 0;
    ObjectTextToBinary(PanelStr, MemStr);
    MemStr.Position := 0;
    MemStr.ReadComponent(pnlTarget);
  finally
    MemStr.Free;
  end;

Registration can be of course moved to elsewhere, like form creation or unit initialization.

You can also transfer ownership of the controls back to the form if it's required, like in the saving code.

Upvotes: 3

Related Questions