tags:

views:

877

answers:

5
A: 

i would rather suggest you convert such kind of records to classes

stanleyxu2005
+4  A: 

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.

dummzeuch
Would that work with the advanced record structure in delphi that allows records to have properties?
I don't think a record can have published properties, they can only be private and public (protected doesn't make sense because they do not support inheritance).
dummzeuch
+1  A: 

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]]);
Bruce McGee
I have tried that before (actually formatting as CSV) but the advantages are diminished due to all the data conversion to strings, plus in this case I have multi-line strings, which will require further formatting to fit this concept.
You can avoid some of the conversions by using a TMemIniFile instead of a TStringList. The solution for multi-line strings might be to sway CRLF with a neutral character (#7) befor saving and swapping it back after loading.
Bruce McGee
A: 

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

Brian Frost
+1  A: 

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
Jeroen Pluimers
Could you give me an example of what you mean of using the Tcomponent streaming mechanism or the RTTI to get it into INI files? A web reference link would be fine.