In the first article I demonstrated some features of
TParams we can use at every day development to hold, process and move data
structures around. But what if we want to persist such data and come back to
them later?
As you will see, persisting is very easy and is based on Delphi’s component streaming mechanism. It is the same core mechanism used by IDE to save and load a form’s design in .dfm files. TStream class introduces methods that work in conjunction with components and filers for loading and saving components in simple and inherited forms. The two TStream methods needed are ReadComponent & WriteComponent and their definitions are:
No, we cannot. Component streaming works with TComponent & descendants and TParams is not one of these, it actually derives from TPersistent->TCollection.
But this is something easily fixed just by declaring a TComponent with a published TParams property. The streaming mechanism can then deal with this component and “magically” save and load the TParams collection.
Here is such a declaration, very simple and clean:
Feel free to modify the above code as per your needs and if you make any enhancements please contact me.
As you will see, persisting is very easy and is based on Delphi’s component streaming mechanism. It is the same core mechanism used by IDE to save and load a form’s design in .dfm files. TStream class introduces methods that work in conjunction with components and filers for loading and saving components in simple and inherited forms. The two TStream methods needed are ReadComponent & WriteComponent and their definitions are:
function ReadComponent(Instance: TComponent): TComponent; procedure WriteComponent(Instance: TComponent);Delphi help also introduces two example functions to show how to use the built-in component streaming support to convert any component into a string and convert that string back into a component. These functions are a long time ago in my utilities library!
function ComponentToString(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; function StringToComponent(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;Now that we have all the streaming functionality in our hands we can stream in & out a TParams collection, or we cannot?
No, we cannot. Component streaming works with TComponent & descendants and TParams is not one of these, it actually derives from TPersistent->TCollection.
But this is something easily fixed just by declaring a TComponent with a published TParams property. The streaming mechanism can then deal with this component and “magically” save and load the TParams collection.
Here is such a declaration, very simple and clean:
TParamsStorage = class(TComponent) protected FParams: TParams; published property Params: TParams read FParams write FParams; end;Now that we have the ability to write and read a TParams collection to and from a string, we can persist it anywhere we want, a local variable, a file, a stream, a database field etc. I personally have a function and procedure to automate the process of converting TParams to string and vice versa. Here they are:
function ParamsToString(Params: TParams): string; var ps: TParamsStorage; begin ps := TParamsStorage.Create(nil); try ps.Params := Params; Result := ComponentToString(ps); finally ps.Free; end; end; procedure StringToParams(Value: string; Params: TParams); var ps: TParamsStorage; begin ps := TParamsStorage.Create(nil); try ps.Params := Params; ps.Params.Clear; StringToComponent(Value,ps); finally ps.Free; end; end;Another interesting effect of having a TParams collection in a string is that we can store this string in a single TParam object, effectively creating a tree structure of TParam collections!You can investigate the whole idea in the code behind the recursive function I use to create/update such structures:
TParamProps = record Name: string; DataType: TFieldType; Value: variant; end; function CreateTParams(const aParams: array of TParamProps): TParams; var i: integer; begin Result := TParams.Create; for i:=0 to High(aParams) do Result.CreateParam(aParams[i].DataType,aParams[i].Name,ptUnknown).Value := aParams[i].Value; end; function UpdateParam( Params: TParams; const //The root TParams collection Path: array of string; //TParam names hierarchy path FldType: TFieldType; //DataType of TParam to create Value: Variant //Value of TParam to create/update ): Boolean; //True always var WP: TParams; P: TParam; A: array of string; i: integer; begin if Length(Path) = 1 then begin P := Params.FindParam(Path[0]); if not Assigned(P) then P := Params.CreateParam(FldType,Path[0],ptUnKnown); P.Value := Value; Result := True; end else begin P := Params.FindParam(Path[0]); if not Assigned(P) then P := Params.CreateParam(ftString,Path[0],ptUnKnown); WP := TParams.Create; try if P.AsString <> '' then StringToParams(P.AsString, WP); SetLength(A,Length(Path)-1); for i:=0 to Length(A)-1 do A[i] := Path[i+1]; Result := UpdateParam(WP,A,FldType,Value); P.AsString := UtilDB.ParamsToString(WP); finally WP.Free; end; end; end;Have fun developing, because development is fun!
Feel free to modify the above code as per your needs and if you make any enhancements please contact me.