How can I save properties of a TList to a stream? I need the entire list to be saved as a whole and not as individual objects.
A TList doesn't have any intrinsic streaming capability built into it, but it is very easy to stream anything that you want with a little elbow grease. Think about it: a stream is data. Classes have properties, whose values are data. It isn't too hard to write property data to a stream. Here's a simple example to get you going. This is but just one of many possible approaches to saving object property data to a stream: unit uStreamableExample;
procedure TPerson.SaveToStream(Stream: TStream); begin WriteString(Stream, FName); WriteDateTime(Stream, FBirthDate); end;
{ TStreamableList }
procedure TStreamableList.Add(Item: TStreamableObject); begin FItems.Add(Item); end;
procedure TStreamableList.Clear; begin FItems.Clear; end;
constructor TStreamableList.Create; begin FItems := TObjectList.Create; end;
procedure TStreamableList.Delete(Index: LongInt); begin FItems.Delete(Index); end;
destructor TStreamableList.Destroy; begin FItems.Free; inherited; end;
function TStreamableList.FindClass(const AClassName: String): TStreamableObjectClass; begin Result := TStreamableObjectClass(Classes.FindClass(AClassName)); end;
function TStreamableList.Get_Count: LongInt; begin Result := FItems.Count; end;
function TStreamableList.Get_Objects(Index: LongInt): TStreamableObject; begin Result := FItems[Index] as TStreamableObject; end;
procedure TStreamableList.LoadFromStream(Stream: TStream); var StreamCount: LongInt; I: Integer; S: String; ClassRef: TStreamableObjectClass; begin StreamCount := ReadLongInt(Stream); for I := 0to StreamCount - 1do begin S := ReadClassName(Stream); ClassRef := FindClass(S); Add(ClassRef.CreateFromStream(Stream)); end; end;
procedure TStreamableList.SaveToStream(Stream: TStream); var I: Integer; begin WriteLongInt(Stream, Count); for I := 0to Count - 1do begin WriteClassName(Stream, Objects[I].ClassName); Objects[I].SaveToStream(Stream); end; end;
{ TStreamableObject }
constructor TStreamableObject.CreateFromStream(Stream: TStream); begin inherited Create; LoadFromStream(Stream); end;
function TStreamableObject.ReadClassName(Stream: TStream): ShortString; begin Result := ReadString(Stream); end;
function TStreamableObject.ReadCurrency(Stream: TStream): Currency; begin Stream.Read(Result, SizeOf(Currency)); end;
function TStreamableObject.ReadDateTime(Stream: TStream): TDateTime; begin Stream.Read(Result, SizeOf(TDateTime)); end;
function TStreamableObject.ReadLongInt(Stream: TStream): LongInt; begin Stream.Read(Result, SizeOf(LongInt)); end;
function TStreamableObject.ReadString(Stream: TStream): String; var L: LongInt; begin L := ReadLongInt(Stream); SetLength(Result, L); Stream.Read(Result[1], L); end;
procedure TStreamableObject.WriteClassName(Stream: TStream; const Value: ShortString); begin WriteString(Stream, Value); end;
procedure TStreamableObject.WriteCurrency(Stream: TStream; const Value: Currency); begin Stream.Write(Value, SizeOf(Currency)); end;
procedure TStreamableObject.WriteDateTime(Stream: TStream; const Value: TDateTime); begin Stream.Write(Value, SizeOf(TDateTime)); end;
procedure TStreamableObject.WriteLongInt(Stream: TStream; const Value: LongInt); begin Stream.Write(Value, SizeOf(LongInt)); end;
procedure TStreamableObject.WriteString(Stream: TStream; const Value: String); var L: LongInt; begin L := Length(Value); WriteLongInt(Stream, L); Stream.Write(Value[1], L); end;
procedure TForm1.LoadButtonClick(Sender: TObject); var List: TStreamableList; Stream: TStream; Instance: TStreamableObject; I: Integer; begin Stream := TFileStream.Create(Path + DEFAULT_FILENAME, fmOpenRead); try List := TStreamableList.Create; try List.LoadFromStream(Stream); for I := 0to List.Count - 1do begin Instance := List[I]; if Instance is TPerson then ShowMessage(TPerson(Instance).Name); if Instance is TCompany then ShowMessage(TCompany(Instance).Name); end; finally List.Free; end; finally Stream.Free; end; end;
procedure TForm1.FormCreate(Sender: TObject); begin Path := ExtractFilePath(Application.ExeName); end;
Answer 2: The solution above will work, but it forces you to implement streaming support for each of the TStreamableObject objects. Delphi has already implemented this mechanism infor the TPersistent classand the TComponent class, and you can use this mechanism. The class I include here does the job. It holds classes that inherit from TUmbCollectionItem (which in turn inherits from Delphi TCollectionItem), and handles all the streaming of the items. As the items are written with the Delphi mechanism, all published data is streamed. Notes: This class does not support working within the delphi IDE like TCollection. All objects inheriting from TUmbCollectionItem must be registered using the Classes.RegisterClass function. All objects inheriting from TUmbCollectionItem must implement the assign function. By default, the TUmbCollection owns its items (frees them when the collection is freed), but this functionality can be changed. unit UmbCollection;
type TUmbCollectionItemClass = Classof TUmbCollectionItem; TUmbCollectionItem = class(TCollectionItem) private FPosition: Integer; public {when overriding this method, you must call the inherited assign.} procedure Assign(Source: TPersistent); Override; published {the position property is used by the streaming mechanism to place the object in the right position when reading the items. do not use this property.} property Position: Integer read FPosition write FPosition; end;
TUmbCollection = class(TObjectList) private procedure SetItems(Index: Integer; Value: TUmbCollectionItem); function GetItems(Index: Integer): TUmbCollectionItem; public function Add(AObject: TUmbCollectionItem): Integer; function Remove(AObject: TUmbCollectionItem): Integer; function IndexOf(AObject: TUmbCollectionItem): Integer; function FindInstanceOf(AClass: TUmbCollectionItemClass; AExact: Boolean = True; AStartAt: Integer = 0): Integer; procedure Insert(Index: Integer; AObject: TUmbCollectionItem);
procedure TUmbCollection.WriteToStream(AStream: TStream); var Writer: TWriter; CollectionList: TObjectList; Collection: TCollection; ItemClass: TUmbCollectionItemClass; ObjectWritten: arrayof Boolean; i, j: Integer; begin Writer := TWriter.Create(AStream, 1024); CollectionList := TObjectList.Create(True); try Writer.WriteListBegin; {init the flag array and the position property of the TCollectionItem objects.} SetLength(ObjectWritten, Count); for i := 0to Count - 1do begin ObjectWritten[i] := False; Items[i].Position := i; end; {write the TCollectionItem objects. we write first the name of the objects class, then write all the object of the same class.} for i := 0to Count - 1do begin if ObjectWritten[i] then Continue; ItemClass := TUmbCollectionItemClass(Items[i].ClassType); Collection := TCollection.Create(ItemClass); CollectionList.Add(Collection); {write the items class name} Writer.WriteString(Items[i].ClassName); {insert the items to the collection} for j := i to Count - 1do if ItemClass = Items[j].ClassType then begin ObjectWritten[j] := True; (Collection.Add as ItemClass).Assign(Items[j]); end; {write the collection} Writer.WriteCollection(Collection); end; finally CollectionList.Free; Writer.WriteListEnd; Writer.Free; end; end;
{ TUmbCollectionItem }
procedure TUmbCollectionItem.Assign(Source: TPersistent); begin if Source is TUmbCollectionItem then Position := (Source as TUmbCollectionItem).Position else inherited; end;