Delphi:将对象数组保存到文件中。(序列化)
我正在构建一个3D应用程序。我在表单上有一个3D布局(TLayout3D),在运行时在其中创建和定位TSphere。球体是我创建的自定义TNode类的一部分:Delphi:将对象数组保存到文件中。(序列化),delphi,serialization,file-handling,Delphi,Serialization,File Handling,我正在构建一个3D应用程序。我在表单上有一个3D布局(TLayout3D),在运行时在其中创建和定位TSphere。球体是我创建的自定义TNode类的一部分: TNode = Class; Sphere :TSphere; ID :String; NodeType :string; TotalDistance :integer; //used in Dijkstras algorithm End; 我有一个由TNode类组成的数组 NodesArray : array [1..100] o
TNode = Class;
Sphere :TSphere;
ID :String;
NodeType :string;
TotalDistance :integer; //used in Dijkstras algorithm
End;
我有一个由TNode类组成的数组
NodesArray : array [1..100] of TNode;
我需要知道如何保存阵列,然后从文件中加载,这样以后就不必“手动”再次创建阵列。
非常感谢您的帮助。虽然我同意David的观点,即今天的JSON将是持久化业务对象和容器的有效选择,但我想在这里发布使用TCollection和TCollectionItem的代码。这是Delphi自此以来一直使用的旧方法,将组件属性流式输出到dfm文件 但这里有一个警告。我已经测试了这个代码,但它不工作。。。不是因为它已损坏(我多年来一直使用类似的代码来持久化业务对象),而是因为TSphere不支持TPersistent Assign和AssignTo接口。为此,集合项的属性必须是简单的数据类型和记录,或者是正确的持久性实现。TSphere不是这两个 无论如何,代码如下: 接口
Type
TNode = Class(TCollectionItem)
Private
FSphere : TSphere;
FID : String;
FNodeType : String;
FTotalDistance: integer;
Procedure SetSphere(Const Value: TSphere);
Public
Constructor Create(Collection: TCollection); Override;
Destructor Destroy; Override;
Procedure Assign(Source: TPersistent); Override;
Published
Property Sphere : TSphere Read FSphere Write SetSphere;
Property ID : String Read FID Write FID;
Property NodeType : String Read FNodeType Write FNodeType;
Property TotalDistance: integer Read FTotalDistance Write FTotalDistance; // used in Dijkstras algorithm
End;
Type
TNodes = Class(TCollection)
Private
Function GetItem(Index: integer): TNode;
Procedure SetItem(Index: integer; Value: TNode);
Public
Constructor Create; Reintroduce;
Function Add: TNode;
Procedure LoadFromFile(Const Filename: String);
Procedure LoadFromStream(S: TStream);
Procedure SaveToFile(Const Filename: String);
Procedure SaveToStream(S: TStream);
Property Items[Index: integer]: TNode Read GetItem Write SetItem; Default;
End;
Type
TNodesWrapper = Class(TComponent)
Private
FCollection: TNodes;
Public
Constructor Create(AOwner: TComponent); Override;
Destructor Destroy; Override;
Published
Property Collection: TNodes Read FCollection Write FCollection;
End;
和实施
{ TNode }
Procedure TNode.Assign(Source: TPersistent);
Begin
If Source Is TNode Then Begin
If Assigned(Collection) Then
Collection.BeginUpdate;
Try
Sphere := TNode(Source).Sphere;
ID := TNode(Source).ID;
NodeType := TNode(Source).NodeType;
TotalDistance := TNode(Source).TotalDistance;
Finally
If Assigned(Collection) Then
Collection.EndUpdate;
End;
End
Else
Inherited;
End;
Constructor TNode.Create(Collection: TCollection);
Begin
Inherited;
FSphere := TSphere.Create(Nil);
// Set default values here
End;
Destructor TNode.Destroy;
Begin
FreeAndNil(FSphere);
Inherited;
End;
Procedure TNode.SetSphere(Const Value: TSphere);
Begin
FSphere.Assign(Value);
End;
{ TNodes }
Function TNodes.Add: TNode;
Begin
Result := TNode(Inherited Add);
End;
Constructor TNodes.Create;
Begin
Inherited Create(TNode);
End;
Function TNodes.GetItem(Index: integer): TNode;
Begin
Result := TNode(Inherited GetItem(Index));
End;
Procedure TNodes.LoadFromFile(Const Filename: String);
Var
S: TFileStream;
Begin
S := TFileStream.Create(Filename, fmOpenRead);
Try
LoadFromStream(S);
Finally
S.Free;
End;
End;
Procedure TNodes.LoadFromStream(S: TStream);
Var
Wrapper: TNodesWrapper;
SBin : TMemoryStream;
Begin
SBin := TMemoryStream.Create;
Wrapper := TNodesWrapper.Create(Nil);
Try
ObjectTextToBinary(S, SBin);
SBin.Position := 0;
SBin.ReadComponent(Wrapper);
Assign(Wrapper.Collection);
Finally
Wrapper.Free;
SBin.Free;
End;
End;
Procedure TNodes.SaveToFile(Const Filename: String);
Var
S: TStream;
Begin
S := TFileStream.Create(Filename, fmCreate);
Try
SaveToStream(S);
Finally
S.Free;
End;
End;
Procedure TNodes.SaveToStream(S: TStream);
Var
Wrapper: TNodesWrapper;
SBin : TMemoryStream;
Begin
SBin := TMemoryStream.Create;
Wrapper := TNodesWrapper.Create(Nil);
Try
Wrapper.Collection.Assign(Self);
SBin.WriteComponent(Wrapper);
SBin.Position := 0;
ObjectBinaryToText(SBin, S);
Finally
Wrapper.Free;
SBin.Free;
End;
End;
Procedure TNodes.SetItem(Index: integer; Value: TNode);
Begin
Inherited SetItem(Index, Value);
End;
{ TNodesWrapper }
Constructor TNodesWrapper.Create(AOwner: TComponent);
Begin
Inherited;
FCollection := TNodes.Create;
End;
Destructor TNodesWrapper.Destroy;
Begin
FreeAndNil(FCollection);
Inherited;
End;
t收集仍在使用中,尽管有些代码对于习惯使用泛型容器的人来说可能很奇怪。。。其中大部分与类型转换和将一个TCollectionItem类链接到其特定的TCollection类有关
神奇之处在于流ReadComponent和WriteComponent方法。不幸的是,由于TCollection不是TComponent,因此必须将其包装在TComponent中。。。这就是Tnodes包装器的用途
它可能看起来很奇怪和复杂,但其中的大部分可以抽象为一个通用的TCollection子体,它增加了加载和保存到File/Stream/String的能力。所以大部分代码可以隐藏在引擎盖下
而且。。。我重复一遍仅当要持久化的属性本身是可持久化的时,此选项才起作用。。。而TSphere不是。所以我现在想知道FireMonkey 3D表单是如何保存它们的,因为我还没有真正开发3D应用程序。不要使用数组和静态大小/绑定的东西!应用“一个责任”规则。为实体创建序列化程序。在这种情况下,您可以改变主意,为任何格式创建任意数量的序列化程序。您的代码变得更加灵活、可变和可测试
TSphere = class
// Entity. Just fields with getter/setter methods
end;
TContext = TDictionary<string><TObject>;
CONST_ctxkey_Factory = 'factory';
IStream = interface (IInvokable )
procedure load( var data_; size_ : cardinal );
procedure store( var data_; size_ : cardinal );
end;
ISerializer = interface ( TInvokable )
procedure load( ctx_ : TContext );
procedure store( ctx_ : TContext );
end;
TSerializer = class ( TInterfaceObject, ISerializer )
protected
// Attributes
fObject : TObject;
fStream : IStream;
public
constructor create( object_ : TObject; stream_ : IStream );
// Realized methods (ISerializer)
procedure load( ctx_ : TContext ); virtual; abstract;
procedure store( ctx_ : TContext ); virtual; abstract;
end;
TSphereSerializer_XML = class ( TSerializer )
public
// Overriden methods
procedure load( ctx_ : TContext ); override;
procedure store( ctx_ : TContext ); override;
end;
TSpheresMainSerializer_BIN = class ( TSerializer )
public
// Overriden methods
procedure load( ctx_ : TContext ); override;
procedure store( ctx_ : TContext ); override;
end;
TSpheresMainSerializer_BIN.store( ctx_ : TContext );
var
spheres : TSphereList;
sf : TSerializerContext;
sph : TSphere;
iSe : ISerializer;
begin
spheres := TSphereList( fObject );
sf := ctx_.items[CONST_ctxkey_Factory];
fStream.write( version_number, sizeOf( cardinal ) );
fStream.write( spheres.count, sizeOf( cardinal ) );
for sph in shperes_ do
begin
iSe := sf.createShpereSerializer( sph );
iSe.store( ctx );
end;
end;
TSphereSerializer_BIN = class ( TSerializer )
public
// Overriden methods
procedure load( ctx_ : TContext ); override;
procedure store( ctx_ : TContext ); override;
end;
TSphereList = TList<TSphere>;
TSerializerFactory = class
public
// It creates serializers for XML format
function createContext : TContext; virtual;
function createSpheresMainSerializer( spheres_ : TSphereList; stream_ : IStream ) : ISerializer; virtual; abstract;
function createSphereSerializer( sphere_ : TSphere; stream_ : IStream ) : ISerializer; virtual; abstract;
end;
TSerializerFactory_BIN = class ( TSerializerFactory )
public
// It creates serializers for binary format
function createSpheresMainSerializer( spheres_ : TSphereList; stream_ : IStream ) : ISerializer; override;
function createSphereSerializer( sphere_ : TSphere; stream_ : IStream ) : ISerializer; override;
end;
function TSerializerFactory_BIN.createSpheresMainSerializer( spheres_ : TSphereList; stream_ : IStream ) : ISerializer;
begin
result := TSpheresMainSerializer_BIN.create( TObject( spheres_ ), stream_ );
end;
function TSerializerFactory_BIN.createSphereSerializer( sphere_ : TSphere; stream_ : IStream ) : ISerializer;
begin
result := TSphereSerializer_BIN.create( sphere_, stream_ );
end;
我知道它太厚,还是太长,乍一看。。。但也许有用!:) 一种方法是去掉数组并使用TCollection和TCollectionItem的后代。有了这些,再加上使用发布的属性,您几乎可以免费获得流式处理和持久性:)这就是IDE用来处理可视组件的arraylike属性的方法。你想怎么做?你喜欢什么方法?使用固定长度数组不是一个好主意。当您需要100多个元素时会发生什么?使用集合或动态数组,或
TList
@Frazz感谢您的回复。如果不太麻烦的话,你能给我一个你的方法的代码示例吗?@DavidHeffernan我可以使用动态数组,然后稍后设置长度。我不知道你所说的我喜欢的方法是什么意思。你推荐什么?我只需要保存它,然后在关闭程序后加载它。感谢您的回复。一个明显的选择是使用JSON。有许多帖子描述了如何做到这一点。
procedure saveSpheresToStream( spheres_ : TSphereList; stream_ : IStream; sf_ : TSerializerFactory );
var
ctx : TContext;
iSe : ISerializer;
begin
try
ctx := sf_.createContext;
ctx.add( CONST_ctxkey_Factory, sf_ );
try
iSe := sf_.createSpheresMainSerializer( spheres_, stream_ );
iSe.store( ctx );
finally
ctx.free;
end;
end;