Title: A persistent form (TPersistentForm)
Question: Nearly every program that wants to present itself at the next startup in the same way as before, needs many read and write commands to the registry to save the state. Is there a way to automate this ?
TPersistentForm is a TForm descent that saves and restores AUTOMATICALLY the properties from all controls within the form into the registry, such as Text, Checked, Position, Top, Left etc. (considered types: float, integer, char, string, boolean).
Look at the article...
Answer:
The component described in this article can be downloaded here: persform_source.zip
The normal way of a Delphi application to save the state of the user interface is to manually write and read properties of VCL components to the registry with the TRegistry class.
Many things have to saved in every application:
- Size and position of the windows
- Status of programoptions (radiobuttons, checkboxes, etc.)
- Last opened directory and file
- Position of a splitter (in explorer interfaces)
- Marked menu entries
However - Delphi provides a much more elegant way to this. This works with RTTI (Runtime Type Information) - the ability to get information about classes at runtime.
The create a form-class that automaticalls stores it's state we need a new class:
type
TPersistentForm = class(TForm)
public
constructor Create(AOwner:
TComponent); override;
destructor Destroy; override;
end;
The methods create and destroy must be overwritten to have entrypoints when the application or form is started and terminated. To examine all the VCLs we use the component-list:
for i := 0 to ComponentCount-1 do
Now comes a new function that is defined in the unit TypeInfo.pas:
'GetPropList' lists all properties of a given class:
uses TypInfo;
var PropAmount: integer;
...
PropAmount := GetPropList(Components[i].ClassInfo,tkProperties,nil);
The last parameter (NIL) specifies, that the function only returns the amount of properties. Than we can reservate enough memory to call the function again with a pointer on this memoryblock:
var PropInfos: PPropList;
...
GetMem(PropInfos,PropAmount*SizeOf(PPropInfo));
GetPropList(Components[i].ClassInfo,tkProperties,PropInfos);
To read the results we need the struct TPropInfo. This procedure is always the same - if we want to read or write properties. All the basic types of the properties - like Integer, Character, String or Boolean - should be considered. Therefore this is handled in to basic functions:
function GetPropAsString(Obj: TObject; Info: PPropInfo): string;
procedure SetPropFromString(Obj: TObject; Info: PPropInfo; Str: string);
With these two functions the code inside the constructer and destructer looks like this,
Create:
for ii := 0 to PropAmount-1 do
begin
hStr:= hReg.ReadString(PropInfos^[ii]^.Name);
if hStr '' then
SetPropFromString(Components[i], PropInfos^[ii],hStr);
end;
Destroy:
for ii := 0 to PropAmount-1 do
begin
hStr := GetPropAsString(Components[i], PropInfos^[ii]);
if hStr '' then
hReg.WriteString(PropInfos^[ii]^.Name,hStr);
end;
where 'hReg' is from type TRegistry.
Let's have a close look how the 'read'.
The structure PropInfo is giving an answer which type the property has. A case can be used for this:
function GetPropAsString(Obj: TObject; Info: PPropInfo): string;
begin
case Info^.PropType^.Kind of
tkInteger: begin end;
tkChar: begin end;
tkString: begin end;
...
end; // case
end;
To actually get the value there are functions like GetStrProp:
tkString:
Result := GetStrProp(Obj, Info);
There are some special properties. The integer property Color for example holds values that are represented with constants like clRed. To work with these constants instead of simple numbers you have to use this:
var hInt: integer;
...
tkInteger:
begin
hInt := GetOrdProp(Obj,Info);
if (PropType^.Name = 'TColor') then
ColorToIdent(hInt,Result);
end;
The 'write'-procedure works the same but uses SetStrProp (and other functions like this) instead.
With this article there is a file attached that contains the whole implementation of TPersistentForm. There is also a 'FilterList' realized where to specify which component should be considered in the saveing-process and which not. Otherwise there will be too many redundant items in the registry.
The pascal unit that contains TPersistentForm comes with a demo project (no installation required):