Reputation: 469
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:
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:
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
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
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