Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/url/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Delphi 如何使用readComponent()从子对象反序列化字段_Delphi - Fatal编程技术网

Delphi 如何使用readComponent()从子对象反序列化字段

Delphi 如何使用readComponent()从子对象反序列化字段,delphi,Delphi,我正在使用writeComponent()序列化对象和子对象。这似乎没有任何问题。在以下示例中,我序列化了TConfigData的对象和TFoo的子对象: object TConfigData myInteger = 999 object TFoo value = 777 end end 但是,当我尝试将其读回时,ReadComponent仅恢复根对象中的值myInteger,它无法恢复子对象TFoo中的值,该子对象TFoo设置为零。我在下面附上完整的代码。两个主要类是TC

我正在使用writeComponent()序列化对象和子对象。这似乎没有任何问题。在以下示例中,我序列化了TConfigData的对象和TFoo的子对象:

object TConfigData
  myInteger = 999
  object TFoo
    value = 777
  end
end
但是,当我尝试将其读回时,ReadComponent仅恢复根对象中的值myInteger,它无法恢复子对象TFoo中的值,该子对象TFoo设置为零。我在下面附上完整的代码。两个主要类是TConfigData和该TFoo中的TConfigData。我已经在互联网上进行了广泛的搜索,但我不明白为什么它无法读取TFoo.value

关于如何让阅读发挥作用,有什么建议吗?(使用XE6)。我相信有一个简单的解释,但我暂时无法解释

unit ufMain;

interface

uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,    
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, V     
Vcl.StdCtrls;

type
  TFoo = class (TComponent)
   private
     fValue : integer;
   published
     property value : integer read fValue write fValue;
  end;

  TConfigData = class (TComponent)
     private
       fInteger : integer;
       fFoo : TFoo;  // <- Subobject
       function ComponentToStringProc(Component: TComponent): string;
       class function StringToComponentProc(Value: string): TComponent;
     protected
       procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
      function   GetChildOwner: TComponent; override;
      published
       property myInteger : Integer read fInteger write fInteger;
       property foo : TFoo read fFoo write fFoo;
     public
       procedure save (fileName : string);
       class function  load (fileName : string) : TConfigData;
       function  getConfigStreamString : string;
       constructor Create (AOwner : TComponent); override;
       destructor Destroy; override;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

Uses IOUTils;

procedure TConfigData.GetChildren(Proc: TGetChildProc; Root: TComponent);
var i : Integer;
begin
  inherited GetChildren(Proc, Root);
  for i := 0 to ComponentCount - 1 do
    Proc(Components[i]);
end;

function TConfigData.GetChildOwner: TComponent;
begin
  result := Self;
end;

constructor TConfigData.Create (AOwner : TComponent);
begin
  inherited;
  fFoo := TFoo.Create (self);
  // foo.SetSubComponent (True); <- I don't want to use this because it flattens the dfm file.
end;

destructor TConfigData.Destroy;
begin
  fFoo.Free;
  inherited;
end;

function TConfigData.getConfigStreamString : string;
begin
   result := ComponentToStringProc (self);
end;

procedure TConfigData.save (fileName : string);
var configStr : string;
begin
  configStr := ComponentToStringProc (self);
  TFile.WriteAllText (fileName, configStr);
end;

class function TConfigData.load (fileName : string) : TConfigData;
var configStr : string;
begin
  configStr := TFile.ReadAllText (fileName);
  result := StringToComponentProc (configStr) as TConfigData;
end;

function TConfigData.ComponentToStringProc(Component: TComponent): string;
var
  BinStream:TMemoryStream;
  StrStream: TStringStream;
  s: string;
begin
  BinStream := TMemoryStream.Create;
  try
    StrStream := TStringStream.Create(s);
    try
      BinStream.WriteComponent(Component);
      BinStream.Seek(0, soFromBeginning);
      ObjectBinaryToText(BinStream, StrStream);
      StrStream.Seek(0, soFromBeginning);
      Result:= StrStream.DataString;
    finally
      StrStream.Free;
    end;
  finally
    BinStream.Free
  end;
end;

class function TConfigData.StringToComponentProc(Value: string): TComponent;
var
  StrStream:TStringStream;
  BinStream: TMemoryStream;
begin
  StrStream := TStringStream.Create(Value);
  try
    BinStream := TMemoryStream.Create;
    try
      ObjectTextToBinary(StrStream, BinStream);
      BinStream.Seek(0, soFromBeginning);
      Result:= BinStream.ReadComponent(nil);
    finally
      BinStream.Free;
    end;
  finally
    StrStream.Free;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var config : TConfigData;
    configStr : string;
begin
  config := TConfigData.Create (nil);
  config.myInteger := 999;
  config.foo.value := 777;
  config.save('c:\\tmp\\config.dat');
  Memo1.text := config.getConfigStreamString;
end;

procedure TForm1.Button2Click(Sender: TObject);
var config : TConfigData;
begin
  config := TConfigData.load ('c:\\tmp\\config.dat');
  Memo1.Clear;
  Memo1.Lines.Add(inttostr (config.myInteger));
  Memo1.Lines.Add(inttostr(config.foo.value));
end;

initialization
 RegisterClasses([TConfigData, TFoo]);
end.
unitufmain;
接口
使用Winapi.Windows、Winapi.Messages、System.SysUtils、System.Variants、,
系统类、Vcl图形、Vcl控件、Vcl窗体、Vcl对话框、V
Vcl.StdCtrls;
类型
TFoo=类(TComponent)
私有的
fValue:整数;
出版
属性值:整数读fValue写fValue;
结束;
TConfigData=类(TComponent)
私有的
fInteger:整数;

fFoo:TFoo;// 我不知道如何获得阅读工作,我只能解释代码有什么问题

将按钮2处理程序替换为

procedure TForm1.Button2Click(Sender: TObject);
var config : TConfigData;
  I: Integer;

begin
  config := TConfigData.load ('c:\temp\config.dat');
  Memo1.Clear;
  Memo1.Lines.Add(inttostr (config.myInteger));
  Memo1.Lines.Add(inttostr(config.ComponentCount));
  for I:= 0 to config.ComponentCount - 1 do
    Memo1.Lines.Add(inttostr((config.Components[I] as TFoo).value));
end;

现在,引擎盖下发生了什么事情变得越来越清楚了。从流中加载的
TConfigData
实例包含两个
TFoo
实例-第一个实例在
TConfigData
构造函数中创建,第二个实例由Delphi streaming system创建,第二个实例由“777”值加载

处理子对象有两种不同的方法

其中之一是
SetSubComponent(true)
,或者首先使用
TPersistent
而不是
TComponent
。在这种情况下,
TConfigData
负责在其构造函数中创建子对象,并在析构函数中销毁它。流媒体系统“期望”该组件已经存在,只需修改其字段

但子组件的处理方式不同。它们是由流媒体系统本身创建的,属于由
GetChildOwner
返回的组件(在您的示例中,它是
TConfigData
)。如果子组件没有名称(或者说,名称为空),则不会执行更多操作。但在这种情况下,它有一个名称,流媒体系统会查找同名的已发布字段,将它们分配给新创建的组件。这是它在VCL中的工作方式:
TForm1
(例如)将所有控件作为已发布字段,这些字段指向从.dfm自动加载的控件

在您的情况下,组件
TFoo
已成功加载,并在组件[]中列出,但它与导致在
TConfigData
构造函数中创建的空组件的
foo
属性无关

如何修复它

与子组件相比,子组件与其父组件的连接较弱,预计父组件不知道可以有多少子组件,这适用于大多数控件。这就是为什么不希望在构造函数中创建子对象。如果从一开始就知道需要哪些类型的子组件,那么使用子组件似乎更符合逻辑

然而,它也可以通过子组件完成

使用已发布字段Foo代替属性Foo:

TConfigData = class (TComponent)
     private
       fInteger : integer;
       function ComponentToStringProc(Component: TComponent): string;
       class function StringToComponentProc(Value: string): TComponent;
     protected
       procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
      function   GetChildOwner: TComponent; override;
      published
       Foo: TFoo;
       property myInteger : Integer read fInteger write fInteger;
     public
       procedure save (fileName : string);
       class function  load (fileName : string) : TConfigData;
       function  getConfigStreamString : string;
       constructor Create (AOwner : TComponent); override;
       destructor Destroy; override;
  end;
在构造函数中,我们创建新的TFoo并为其命名:

constructor TConfigData.Create (AOwner : TComponent);
begin
  inherited;
  Foo := TFoo.Create (self);
  Foo.name = 'Foo'; //looks like tautology but it's not!
end;
但当从文件加载时,我们会破坏所有现有组件,因为流媒体系统从头开始创建它们,否则会发生名称冲突。诸如此类:

class function TConfigData.StringToComponentProc(Value: string): TComponent;
var
  StrStream:TStringStream;
  BinStream: TMemoryStream;
begin
  StrStream := TStringStream.Create(Value);
  try
    BinStream := TMemoryStream.Create;
    try
      ObjectTextToBinary(StrStream, BinStream);
      BinStream.Seek(0, soFromBeginning);
      Result:= TConfigFile.Create(nil); //it creates components we don't need
      Result.DestroyComponents; //not any more
      BinStream.ReadComponent(Result); //it reads to component already created
    finally
      BinStream.Free;
    end;
  finally
    StrStream.Free;
  end;
end;
现在应该可以了

也许更优雅的做法是完全删除构造函数
Create(aOwner:TComponent)
(继承就足够了),然后使用另一个构造函数,比如
CreateNew
,它只从您的代码中调用,以防找不到此配置文件。或者,使用过程
InitializeDefault
或类似的smth来代替构造函数,这会将所有字段设置为默认值,并在需要时创建
TFoo


甚至不可能在运行时显式创建
TFoo
,而是从文件或存储默认值的资源加载
TConfigData

谢谢你的回复。你所付出的是有用的。在我的特定应用程序中,我不会有子对象,但如果有多层对象,我想知道它是如何工作的。@rody它会工作得很好。为了实现它,我将引入抽象类TStreamingClass,其中的GetChildren和GetChildOwner方法在TConfigData中实现,因此TConfigInfo和TFoo以及其他组件都可以是TStreamingClass的后代。然后,您可以根据需要使层次结构变得复杂,就像在描述表单的dfm中所做的那样,尽管差别不大。在可视化控件中,表单是所有组件的所有者,而在TConfigData中,所有者与父项相同。@罗迪,这就是为什么在表单中,如果有Panel1上的按钮1,那么您可以编写form1.Button1或简单的按钮1,而根本不提Panel1。在您的代码中,如果ConfigData具有子对象Foo(具有子对象栏),则必须编写ConfigData.Foo.Bar。我认为,这很好,不需要作为父/子和所有者/其组件的两个不同层次结构,这将过于复杂。