Entwickler-Ecke

Dateizugriff - Verschachtelte Objekte speichern


AXMD - Mo 29.08.05 10:59
Titel: Verschachtelte Objekte speichern
Morgen!

Ich muss programmbedingt ein Objekt speichern, d.h. genauer gesagt eine Klasse, die von TObject abstammt. Das Problem: das Objekt hat zusätzlich zu seinen Eigenschaften auch "Unter"objekte. Wie kann ich das speichern? Und wie wieder laden? WriteComponent TFilestream scheidet aus, weil meine Klassen ja von TObject abstammen. Bin für alle Ideen offen :)

AXMD


Moderiert von user profile iconChristian S.: Topic aus CLX / Delphi Language (Object-Pascal) verschoben am Mo 29.08.2005 um 11:27


Christian S. - Mo 29.08.05 11:24

Hallo!

Wie ich Dir schon per ICQ geasgt habe, würde ich das rekursiv machen! Du gibst jedem Objekt eine Methode SaveToStream(var s : TFilestream). Die sieht dann so aus:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
procedure TaClass.SaveToStream(var s : TFilestream);
var
  i : Integer;
begin
  if s = nil then
    exit;

  //Teil 1: Eigene Daten
  s.Write(aVar, sizeOf(aVar));
  s.Write(anotherVar, sizeOf(anotherVar));

  //Teil 2: Daten von Fremdkomponenten
  for i:=0 to High(subObjects) do
    subObjects[i].SaveToStream(s);
end;

procedure TaClass.LoadFromStream(var s : TFilestream);
var
  i : Integer;
begin
  if s = nil then
    exit;

  //Teil 1: Eigene Daten
  s.Read(aVar, sizeOf(aVar));
  s.Read(anotherVar, sizeOf(anotherVar));

  //Teil 2: Daten von Fremdkomponenten
  for i:=0 to High(subObjects) do
    subObjects[i].LoadFromStream(s);
end;


Dadurch, dass jedes Objekt weiß, wievie Daten in Teil 1 stehen (zur Not am Anfang von Teil 1 mitspeichern), weiß es genau, zu welchem Punkt es an die anderen Objekte übergeben muss.

Ist jetzt ohne Delphi zur Hand zu haben geschrieben, aber von der Idee her sollte es gehen.

Grüße
Christian


delfiphan - Mo 29.08.05 14:53

Ich würde eine entsprechende Datenstruktur (z.B. Baum) definieren und die Daten rekursiv dort einfügen. So lässt sich das ganze am Schluss auch als XML Dokument (o.ä.) speichern.
Man muss auch darauf achten, dass man beim Laden die Unterobjekte auch wieder erstellen muss...


Sprint - Mo 29.08.05 15:32

Ich bevorzuge bei sowas immer die Klassen TReader und TWriter. Mal ein Beispiel:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
unit Unit2;

interface

uses
  SysUtils, Classes;

type
  TMyClass = class(TObject)
  private
    FValueA: String;
    FValueB: Integer;
    procedure SaveValues(AFiler: TWriter);
    procedure LoadValues(AFiler: TReader);
  public
    property ValueA: String read FValueA write FValueA;
    property ValueB: Integer read FValueB write FValueB;
  end;

  TMyObject = class(TObject)
  private
    FMyClass: TMyClass;
  public
    constructor Create;
    destructor Destroy; override;
    procedure SaveToFile(const FileName: String);
    procedure LoadFromFile(const FileName: String);
    property MyClass: TMyClass read FMyClass write FMyClass;
  end;

implementation

{ TMyClass }

procedure TMyClass.LoadValues(AFiler: TReader);
begin
  FValueA := AFiler.ReadString;
  FValueB := AFiler.ReadInteger;
end;

procedure TMyClass.SaveValues(AFiler: TWriter);
begin
  AFiler.WriteString(FValueA);
  AFiler.WriteInteger(FValueB);
end;

{ TMyObject }

constructor TMyObject.Create;
begin
  inherited;
  FMyClass := TMyClass.Create;
end;

destructor TMyObject.Destroy;
begin
  FMyClass.Free;
  inherited;
end;

procedure TMyObject.LoadFromFile(const FileName: String);
var
  Handle: Integer;
  Stream: THandleStream;
  Filer: TReader;
begin
  Handle := FileOpen(FileName, fmOpenRead);
  if Handle <> -1 then
  begin
    Stream := THandleStream.Create(Handle);
    Filer := TReader.Create(Stream, 4096);
    try
      Filer.ReadSignature;
      if Filer.ReadIdent = ClassName then
        MyClass.LoadValues(Filer);
    finally
      Filer.Free;
      Stream.Free;
      FileClose(Handle);
    end;
  end;
end;

procedure TMyObject.SaveToFile(const FileName: String);
var
  Handle: Integer;
  Stream: THandleStream;
  Filer: TWriter;
begin
  Handle := FileCreate(FileName);
  if Handle <> -1 then
  begin
    Stream := THandleStream.Create(Handle);
    Filer := TWriter.Create(Stream, 4096);
    try
      Filer.WriteSignature;
      Filer.WriteIdent(ClassName);
      FMyClass.SaveValues(Filer);
    finally
      Filer.Free;
      Stream.Free;
      FileClose(Handle);
    end;
  end;
end;

end.


AXMD - Mo 29.08.05 15:38

user profile icondelfiphan hat folgendes geschrieben:
Man muss auch darauf achten, dass man beim Laden die Unterobjekte auch wieder erstellen muss...


Eben das wird eines des größeren Probleme werden. Vor allem deshalb, weil 4 Kinderklassen von meiner Klasse abstammen, die alle "Unter"objekte sein können.

AXMD


Christian S. - Mo 29.08.05 15:53

user profile iconAXMD hat folgendes geschrieben:
Eben das wird eines des größeren Probleme werden. Vor allem deshalb, weil 4 Kinderklassen von meiner Klasse abstammen, die alle "Unter"objekte sein können.
Um das hinzubekommen, könnte Dir dies [http://www.christian-stelzmann.de/artikel/klassen_registrieren.htm] helfen. Da instanziert eine Mutterklasse beim Laden von Klassen aus einer Datei immer die richtige Kindklasse. Arbeitet auch ganz gut mit dem zusammen, was ich oben geschrieben habe ;-)


AXMD - Mo 29.08.05 18:22

Konnte das Problem dank Christians Ratschlägen und ein bisschen Experimentieren gut lösen :). Für alle die's interessiert: der Code hier. Im Anhang die komplette Unit um die Sache im Kontext zu sehen :)


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
procedure TCircuitElementGroup.SaveToStream(var AStream: TFileStream);
var
  i: Integer;
begin
  inherited;
  i := Length(FElements);
  AStream.Write(i, SizeOf(i)); //Write number of subelements
  AStream.Write(FParallel, SizeOf(FParallel));
  for i := Low(FElements) to High(FElements) do //Write subelements to stream
    FElements[i].SaveToStream(AStream);
end;



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
function LoadFromStream(var AStream: TFileStream): TCustomCircuitElement;
var
  TempID, i: Integer;
  AValue: Double;
  AName: ShortString;
  AParallel: Boolean;
begin
  Result := nil//Initialize
  with AStream do begin
    Read(TempID, SizeOf(TempID)); //ID
    Read(AName, SizeOf(AName)); //Name
    Read(AValue, SizeOf(AValue)); //Value
    case TempID of
      1: Result := TResistor.Create(AValue);
      2: Result := TCapacitor.Create(AValue);
      3: Result := TCoil.Create(AValue);
      4begin
           Result := TCircuitElementGroup.Create;
           Read(TempID, SizeOf(TempID)); //Read number of children
           Read(AParallel, SizeOf(AParallel));
           (Result as TCircuitElementGroup).Parallel := AParallel;
           for i := 0 to TempID - 1 do
             (Result as TCircuitElementGroup).AddElement(LoadFromStream(AStream));
         end;
      end;
    Result.Name := AName;
    end;
end;


AXMD

//EDIT: Datei angehängt :)