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:
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.
































