2016-07-18 4 views
1

私はwriteComponent()を使用してオブジェクトとサブオブジェクトを直列化しています。これは問題なく動作するようです。次の例では、私はTConfigDataの対象とTFooのサブオブジェクトをシリアル化:私は後ろにそれを読むしようとすると、readComponent()を使用してサブオブジェクトからフィールドを逆シリアル化する方法

object TConfigData 
    myInteger = 999 
    object TFoo 
    value = 777 
    end 
end 

しかし、readCompontentが唯一のルートオブジェクト内の値のmyIntegerを回復し、それが値を回復するために失敗しましたそれはゼロに設定するサブオブジェクトTFoo内にある。私は以下のコード全体を囲みます。 2つの主なクラスはTConfigDataとそのTFoo内です。私はインターネットの広範な検索を行ったが、なぜそれが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. 

答えて

1

サブオブジェクトを扱うには2通りの方法があります。

SetSubComponent(true)のいずれか、または最初にTComponentの代わりにTPersistentを使用してください。この場合、コンストラクタにサブオブジェクトを作成し、デストラクタで破棄するのはTConfigDataです。ストリーミングシステムはコンポーネントがすでに存在し、そのフィールドを変更するだけで済みます。

しかし、子コンポーネントは異なる方法で扱われます。それらはストリーミングシステム自体によって作成され、GetChildOwner(あなたの場合はTConfigData)によって返されたコンポーネントに属します。子コンポーネントが名前を持たない(または名前が空である)場合、それ以上のアクションは実行されません。しかし、名前がある場合、ストリーミングシステムは、同じ名前の公開フィールドを探して、新しく作成されたコンポーネントに割り当てます。これはVCLでの動作です:TForm1(たとえば)はすべてのコントロールを公開フィールドとして持ち、これらのフィールドは.dfmから自動的にロードされたコントロールを指します。

TFooコンポーネントが正常に読み込まれ、コンポーネント[]に表示されますが、コンストラクタで作成された空のコンポーネントにつながる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; 

をしかし、ファイルからロードするとき、我々はすべて破棄

代わりのプロパティのFoo、使用、公開フィールドのFoo

既存のコンポーネントは、ストリーミングシステムがゼロから作成するため、名前の競合が発生します。次のようなものがあります:

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をロードすることもできます。

+0

返信いただきありがとうございます。あなたが与えたものは働きます。私の特定のアプリケーションでは、私はサブサブジェクトを持たないでしょうが、オブジェクトの複数のレイヤーを持つ場合、どのように動作するのだろうかと思います。 – rhody

+0

@rhodyそれはうまくいくでしょう。これを実装するには、抽象クラスTStreamingClassをTConfigDataのように実装されたGetChildrenとGetChildOwnerメソッドで導入します。そのため、TConfigInfoとTFooなどのコンポーネントはすべてTStreamingClassの子孫である可能性があります。そして、あなたは、ほとんど違いはありませんが、フォームを記述するdfmのように、階層構造を必要に応じて複雑にすることができます。ビジュアルコントロールでは、フォームはすべてのコンポーネントの所有者ですが、TConfigDataではownerはparentと同じです。 –

+0

@rhodyそのため、Panel1にButton1がある場合は、Panel1を何も言わずにform1.button1またはbutton1と書くだけです。あなたのコードで、ConfigDataにサブオブジェクトBarがあるサブオブジェクトFooがある場合は、ConfigData.Foo.Barを記述する必要があります。私は、それは親愛子と所有者/そのコンポーネントとして2つの異なる階層の必要はないと思う、それはうまくいく、それは複雑すぎるだろう。 –

1

私は読書の仕方を知りません。私はコードの何が間違っているのか説明できます。

は、今ではフードの下で何が起こっているのか明確になってきている

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; 

でButton2のハンドラを交換してください。ストリームからロードしたTConfigDataインスタンスには、TFooという2つのインスタンスが含まれています。最初はTConfigDataコンストラクタで作成され、2つ目はDelphiストリーミングシステムで作成され、2つ目は '777'値によってロードされます。

関連する問題