i would rather suggest you convert such kind of records to classes
No chance at that. A record has no runtime type information attached to it. If you used a class with published properties instead, it would be possible, see the TApplicationStorage/TFormStorage components in the jvcl, the Delphi DFM streaming system and various internet sites for examples.
You could just use a TStringList filled with name/value pairs.
You don't need to know the structure ahead of time, the elements are easy to walk through one at a time or address by index, and it's perfectly suited for saving to ini files.
Use TIniFile.ReadSectionValues to populate your TStringList from a section in your ini file.
var
i: Integer;
begin
FList := TStringList.Create;
FList.Add('a=one');
FList.Add('b=two');
FList.Add('c=three');
Label1.Caption := FList.Values['b'];
for i := 0 to FList.Count - 1 do
Memo1.Lines.Add(FList.Names[i] + ' - ' + FList.Values[FList.Names[i]]);
Yes, but you have to wrap the record into an object and publish the fields as properties. You can then iterate through the properties to read and write to an INI file. I've done this a lot, RTTI is very useful. Bri
Records can have RTTI, but only if it has at least one reference counted field (like string or interface). And that record RTTI is very limited, so it won't help you any furter.
At Delphi Live, it was shown that RTTI will be expanded a lot in Delphi 2010, so it might have expanded record RTTI as well.
I suggest you derive a class from TComponent and use the built-in streaming mechanism, or use RTTI to get it to/from an INI file. Much easier!
--jeroen
Edit: example
First the component:
unit IntegerValueComponentUnit;
interface
uses
Classes;
type
TCustomIntegerValueComponent = class(TComponent)
strict private
FIntegerValue: Integer;
strict protected
function GetIntegerValue: Integer; virtual;
procedure SetIntegerValue(const Value: Integer); virtual;
public
property IntegerValue: Integer read GetIntegerValue write SetIntegerValue;
end;
TIntegerValueComponent = class(TCustomIntegerValueComponent)
published
property IntegerValue;
end;
implementation
function TCustomIntegerValueComponent.GetIntegerValue: Integer;
begin
Result := FIntegerValue;
end;
procedure TCustomIntegerValueComponent.SetIntegerValue(const Value: Integer);
begin
FIntegerValue := Value;
end;
end.
Then the resulting dfm file:
object TIntegerValueComponent
IntegerValue = 33
end
That dfm file is reasonably easy to edit, like ini files, you can make errors, so it is not recommended to be edited by casual users.
You need this unit to get going, it does the basic streaming to/from streams, and the conversion of the binary format into the text format (if memory serves me right, the text format has been the default since Delphi 5 as it is much easier to read).
unit ComponentDfmUnit;
interface
uses
Classes, IntegerValueComponentUnit;
type
TComponentDfm = class
public
class function FromDfm(const Dfm: string): TComponent; static;
class function GetDfm(const Component: TComponent): string; static;
class function LoadFromDfm(const FileName: string): TComponent; static;
class procedure SaveToDfm(const Component: TComponent; const FileName: string); static;
end;
implementation
class function TComponentDfm.FromDfm(const Dfm: string): TComponent;
var
MemoryStream: TMemoryStream;
StringStream: TStringStream;
begin
MemoryStream := TMemoryStream.Create;
try
StringStream := TStringStream.Create(Dfm);
try
ObjectTextToBinary(StringStream, MemoryStream);
MemoryStream.Seek(0, soFromBeginning);
Result := MemoryStream.ReadComponent(nil);
finally
StringStream.Free;
end;
finally
MemoryStream.Free;
end;
end;
class function TComponentDfm.GetDfm(const Component: TComponent): string;
var
MemoryStream: TMemoryStream;
StringStream: TStringStream;
begin
MemoryStream := TMemoryStream.Create;
try
MemoryStream.WriteComponent(Component);
StringStream := TStringStream.Create('');
try
MemoryStream.Seek(0, soFromBeginning);
ObjectBinaryToText(MemoryStream, StringStream);
Result := StringStream.DataString;
finally
StringStream.Free;
end;
finally
MemoryStream.Free;
end;
end;
class function TComponentDfm.LoadFromDfm(const FileName: string): TComponent;
var
DfmStrings: TStrings;
begin
DfmStrings := TStringList.Create;
try
DfmStrings.LoadFromFile(FileName);
Result := TComponentDfm.FromDfm(DfmStrings.Text);
finally
DfmStrings.Free;
end;
end;
class procedure TComponentDfm.SaveToDfm(const Component: TComponent; const FileName: string);
var
DfmStrings: TStrings;
begin
DfmStrings := TStringList.Create;
try
DfmStrings.Text := TComponentDfm.GetDfm(Component);
DfmStrings.SaveToFile(FileName);
finally
DfmStrings.Free;
end;
end;
end.
And then this example should work (first the form code, then the form dfm): The most important line is RegisterClass(TIntegerValueComponent);, as it is easy to forget. The rest of the code is pretty straight forward.
As a bonus, you also see how you can copy a component to the clipboard and paste it back. It streams to/from the clipboard using the binary format.
const
FileName = 'IntegerValue.dfm';
procedure TStreamingDemoForm.ButtonEnabledTimerTimer(Sender: TObject);
begin
SaveButton.Enabled := SaveStyleRadioGroup.ItemIndex <> -1;
LoadButton.Enabled := SaveButton.Enabled;
if SaveStyleRadioGroup.ItemIndex = 0 then
LoadButton.Enabled := FileExists(FileName);
if SaveStyleRadioGroup.ItemIndex = 1 then
LoadButton.Enabled := Clipbrd.Clipboard.HasFormat(CF_COMPONENT);
end;
procedure TStreamingDemoForm.LoadButtonClick(Sender: TObject);
var
IntegerValueComponent: TIntegerValueComponent;
begin
IntegerValueComponent := nil;
if SaveStyleRadioGroup.ItemIndex = 0 then
IntegerValueComponent := LoadUsingFileStream()
else
IntegerValueComponent := LoadUsingClipboard();
try
if Assigned(IntegerValueComponent) then
Log('Loaded: %d', [IntegerValueComponent.IntegerValue])
else
Log('nil during Load');
finally
IntegerValueComponent.Free;
end;
end;
function TStreamingDemoForm.LoadUsingClipboard: TIntegerValueComponent;
var
Component: TComponent;
begin
Result := nil;
RegisterClass(TIntegerValueComponent);
Component := Clipboard.GetComponent(nil, nil);
if Assigned(Component) then
if Component is TIntegerValueComponent then
Result := TIntegerValueComponent(Component);
end;
function TStreamingDemoForm.LoadUsingFileStream: TIntegerValueComponent;
var
Component: TComponent;
begin
Result := nil;
RegisterClass(TIntegerValueComponent);
Component := TComponentDfm.LoadFromDfm(FileName);
if Assigned(Component) then
if Component is TIntegerValueComponent then
Result := TIntegerValueComponent(Component);
end;
procedure TStreamingDemoForm.Log(const Line: string);
begin
LogMemo.Lines.Add(Line);
end;
function TStreamingDemoForm.Log(const Mask: string; const Args: array of const): string;
begin
Log(Format(Mask, Args));
end;
procedure TStreamingDemoForm.SaveButtonClick(Sender: TObject);
var
IntegerValueComponent: TIntegerValueComponent;
begin
IntegerValueComponent := TIntegerValueComponent.Create(nil);
try
IntegerValueComponent.IntegerValue := ValueToSaveSpinEdit.Value;
if SaveStyleRadioGroup.ItemIndex = 0 then
SaveUsingFileStream(IntegerValueComponent)
else
SaveUsingClipboard(IntegerValueComponent);
Log('Saved: %d', [IntegerValueComponent.IntegerValue])
finally
IntegerValueComponent.Free;
end;
end;
procedure TStreamingDemoForm.SaveUsingClipboard(IntegerValueComponent: TIntegerValueComponent);
begin
Clipboard.SetComponent(IntegerValueComponent);
end;
procedure TStreamingDemoForm.SaveUsingFileStream(IntegerValueComponent: TIntegerValueComponent);
begin
TComponentDfm.SaveToDfm(IntegerValueComponent, Filename);
end;
Finally the form dfm:
object StreamingDemoForm: TStreamingDemoForm
Left = 0
Top = 0
Caption = 'StreamingDemoForm'
ClientHeight = 348
ClientWidth = 643
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
DesignSize = (
643
348)
PixelsPerInch = 96
TextHeight = 13
object ValueToSaveLabel: TLabel
Left = 14
Top = 8
Width = 65
Height = 13
Caption = '&Value to save'
FocusControl = ValueToSaveSpinEdit
end
object ValueToSaveSpinEdit: TSpinEdit
Left = 95
Top = 5
Width = 121
Height = 22
MaxValue = 0
MinValue = 0
TabOrder = 0
Value = 0
end
object SaveButton: TButton
Left = 14
Top = 33
Width = 75
Height = 25
Caption = '&Save'
TabOrder = 1
OnClick = SaveButtonClick
end
object LoadButton: TButton
Left = 14
Top = 64
Width = 75
Height = 25
Caption = '&Load'
TabOrder = 2
OnClick = LoadButtonClick
end
object LogMemo: TMemo
Left = 14
Top = 95
Width = 621
Height = 245
Anchors = [akLeft, akTop, akRight, akBottom]
Lines.Strings = (
'LogMemo')
TabOrder = 3
end
object SaveStyleRadioGroup: TRadioGroup
Left = 95
Top = 30
Width = 121
Height = 59
Caption = 'Save st&yle'
Items.Strings = (
'Value.dfm'
'Clipboard')
TabOrder = 4
end
object ButtonEnabledTimer: TTimer
Interval = 100
OnTimer = ButtonEnabledTimerTimer
Left = 270
Top = 6
end
end