Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Array of a custom class as a property

I am trying to use an array of a custom class as a property for my component, but the problem is that the values are not been saved to the component, that means that if I set the values, save everything and open again the project, the values for the component disappears... My code looks like the following:

unit Unit1;

interface

uses  Windows, ExtCtrls,Classes,Controls;

type

  TMyClass=class(TPersistent)
  private
    FName: string;
    FValue: double;
  public
    property Name: string read FName write FName;
    property Value: double read FValue write FValue;
  end;

  TMyComponent= class(TCustomPanel)
  private
    FMyArray: array[0..200] of TMyClass;

    function GetmyArray(Index: Integer): TMyClass;

    procedure SetMyArray(index: Integer; Value: TMyClass);
  public
    property myArray[index: Integer]: TMyClass read GetMyArray write SetMyArray;
  end;

implementation

function TMyComponent.GetmyArray(Index: Integer): TMyClass;
begin
  result:= FmyArray[Index];
end;

procedure TMyComponent.SetMyArray(index: Integer; Value: TMyClass);
begin
  FMyArray[index].FName:= Value.FName;
  FMyArray[index].FValue:= Value.FValue;
end;

end.

I know that that only published properties can be streamed, but the problem is that my property is an array and it can not be published... A suggestion that I had was to use DefineProperties() to provide a custom streaming but I don't see how to do this with an array. Other possibility that I thought was to modify TMyClass to a kind of class that TMyComponent could be the parent of it, like it is done in TChart, which you can add different classes of series to it. But I don't know What class this should be

TMyClass=class(T???????????)

With that I could take out the property MyArray and create TMyClass and add to TMyComponent as the following:

MyArray1.parent:= MyComponent1;
MyArray2.parent:= MyComponent2;
...

. Which one is the better option? Or is there any other better idea?

like image 981
Felipe Avatar asked May 29 '13 16:05

Felipe


2 Answers

The simpliest (and preferred) solution is to change TMyClass to derive from TCollectionItem and change TMyComponent.FMyArray to TOwnedCollection. Then the DFM will stream the items automatically for you, and you gain native design-time support for creating and manipulating TMyClass objects and their properties.

Try this:

unit Unit1;

interface

uses
  Windows, ExtCtrls, Classes, Controls;

type
  TMyClass = class(TCollectionItem)
  private
    FName: string;
    FValue: double;

    procedure SetName(const AValue: string);
    procedure SetValue(AValue: double);
  public
    procedure Assign(ASource: TPersistent); override;
  published
    property Name: string read FName write SetName;
    property Value: double read FValue write SetValue;
  end;

  TMyArray = class(TOwnedCollection)
  private
    function  GetItem(Index: Integer): TMyClass;
    procedure SetItem(Index: Integer; const Value: TMyClass);
  public
    constructor Create(AOwner: TPersistent);
    function  Add: TMyClass; reintroduce;
    function  Insert(Index: Integer): TMyClass; reintroduce;
    property  Items[Index: Integer]: TMyClass read GetItem write SetItem; default;
  end;

  TMyComponent = class(TCustomPanel)
  private
    FMyArray: TMyArray;

    procedure SetMyArray(Value: TMyArray);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property myArray: TMyArray read FMyArray write SetMyArray;
  end;

implementation

procedure TMyClass.Assign(ASource: TPersistent);
begin
  if ASource is TMyClass then
  begin
    with TMyClass(ASource) do
    begin
      Self.FName := Name;
      Self.FValue := Value;
    end;
    Changed(False);
  end else
    inherited;
end;

procedure TMyClass.SetName(const AValue: string);
begin
  if FName <> AValue then
  begin
    FName := AValue;
    Changed(False);
  end;
end;

procedure TMyClass.SetValue(AValue: double);
begin
  if FValue <> AValue then
  begin
    FValue := AValue;
    Changed(False);
  end;
end;

constructor TMyArray.Create(AOwner: TPersistent);
begin
  inherited Create(AOwner, TMyClass);
end;

function TMyArray.GetItem(Index: Integer): TMyClass;
begin
  Result := TMyClass(inherited GetItem(Index));
end;

procedure TMyArray.SetItem(Index: Integer; const Value: TMyClass);
begin
  inherited SetItem(Index, Value);
end;

function TMyArray.Add: TMyClass;
begin
  Result := TMyClass(inherited Add);
end;

function TMyArray.Insert(Index: Integer): TMyClass;
begin
  Result := TMyClass(inherited Insert(Index));
end;

constructor TMyComponent.Create(AOwner: TComponent);
begin
  inherited;
  FMyArray := TMyArray.Create(Self);
end;

destructor TMyComponent.Destroy;
begin
  FMyArray.Free;
  inherited;
end;

procedure TMyComponent.SetMyArray(Value: TMyArray);
begin
  FMyArray.Assign(Value);
end;

end.
like image 120
Remy Lebeau Avatar answered Nov 09 '22 00:11

Remy Lebeau


I'd vote for DefineProperties! The necessary code might look like this (assuming none of the instances in the array is nil):

procedure TMyComponent.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineProperty('MyArray', ReadMyArray, WriteMyArray, true);
end;

procedure TMyComponent.ReadMyArray(Reader: TReader);
var
  N: Integer;
begin
  N := 0;
  Reader.ReadListBegin;
  while not Reader.EndOfList do begin
    Reader.ReadListBegin;
    FMyArray[N].Name := Reader.ReadString;
    FMyArray[N].Value := Reader.ReadFloat;
    Reader.ReadListEnd;
    Inc(N);
  end;
  Reader.ReadListEnd;
end;

procedure TMyComponent.WriteMyArray(Writer: TWriter);
var
  I: Integer;
begin
  Writer.WriteListBegin;
  for I := 0 to High(FMyArray) do begin
    Writer.WriteListBegin;
    Writer.WriteString(FMyArray[I].Name);
    Writer.WriteFloat(FMyArray[I].Value);
    Writer.WriteListEnd;
  end;
  Writer.WriteListEnd;
end;
like image 5
Uwe Raabe Avatar answered Nov 09 '22 00:11

Uwe Raabe