Delphi将流面板保存为文件。

3

今天我的问题涉及到将表单的一部分流式传输到文件中。 在此示例中,我使用Tmemo而不是文件,以便查看流。

这是我的表格:

enter image description here

表单右上角的面板有一些控件,如标签,编辑等。 通过“保存面板”按钮,我将面板保存到TStream中:

以下为代码:

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;

这里是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是一个在窗体的私有部分声明并在创建窗体时创建的TStream对象。

这部分工作很好,正如您在图像右侧看到的那样,表单上的元素已正确注册。

现在我的问题是将这个元素恢复到窗体左下角的面板中。

我尝试了以下例程:

{ 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;

但它没起作用,你可以在下面的图片中看到结果:

enter image description here

我的程序有什么问题,我怎样才能读取流中的所有元素而不是仅仅第一个元素?

有人能帮我解决这个头疼的问题吗?


据我所见,您手头的列表是原始面板中组件的列表,但您正在尝试读取目标面板的属性。这些属性不存在,因为目标面板在源代码中未定义。您需要做的是读取目标面板的属性,并将它们作为包装器放置在您读取的嵌入式组件的属性周围,即在从目标面板读取的内容的结束语句之前放置右侧面板中显示的数据,如果这有意义的话。 - Dsm
2个回答

3
您当前运行的代码有效地将源面板转换为标签。这是因为第一个流式传输的对象是标签,代码仅读取一个组件。也就是说,当读者到达第一个end时,阅读完成,因为流中没有控件。
因此,首先,您必须编写面板 - 仅面板。面板是应该流式传输其子项的面板。为了使其这样做,它必须拥有它的控件。
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;

这会在备忘录中产生如下输出:
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
    ...

请注意标签定义的缩进和所属面板缺少的'end'(它在末尾)。
当加载时,您需要注册类才能使数据流器找到它们:
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;

注册过程当然可以移到其他地方,比如表单创建或单元初始化。

如果需要,在保存代码中,您还可以将控件的所有权转让回表单。


1
只是澄清一下你的第一行。它不会将 TPanel 更改为 TLabel,它仍然是 TPanel。但原始代码中 ReadComponent 做的就是读取第一个对象 (TLabel),忽略它的类型 (文档中有说明),并试图将 Label1 的属性应用到 Panel2 上。由于 TLabel 和 TPanel 具有相同的已发布属性,所以原始代码不会引发异常,并且面板移动到左上角,看起来有点像原始的 Label1。 - Dsm

0

正如我在评论中提到的那样,您需要将数据与Panel2信息一起包围。您还需要注册每个要保存和恢复的控件类型。

这意味着只有加载过程需要更改 - 就像这样:

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;

如前面的答案所述,注册可以放在其他地方。

与前面的答案不同,您不需要先更改控件的所有权。(这只是一条评论 - 不是批评)。这只是我的评论的实现。

我的命名约定与您的不同。我尝试使用相同的名称,但如果我错过了任何内容,请原谅我。


命名很好,我可以只用粘贴运行它 :). 无论如何,我认为值得注意的是,您正在以不同的方式和不同的方式转移所有权:通过手动修改流。因此,控件的所有权不能留在表单中,而是您不必首先更改它。像您一样,这不是批评,只是想注意一下。 - Sertac Akyuz
不错,Sertac!只有一个问题:在 pnlSource 被加载到 pnlTarget 后,pnlTarget 就变成了 pnlSource;是这样的吗? - Eros
@Eros使用Sertacs的解决方案是可以的(除非您首先将其重命名)。使用我的解决方案,不行,它仍然是pnlTarget。 - Dsm
好的DSM,我已经尝试了Sertac的解决方案,现在我尝试你的。 - Eros

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接