views:

145

answers:

2

I want to make a component that includes a list of integers as one of its serialized properties. I know I can't declare a TList<integer> as a published property, because it doesn't descend from TPersistent. I've read that you can define "fake" published properties if you override DefineProperties, but I'm not quite sure how that works, especially when it comes to creating a fake property that's a list, not a single value.

Can someone point me in the right direction?

+1  A: 

The quickest, easiest method to make it happen is to use a TCollection - but you'll pay the price of "decorating" every single Integer with a TCollectionItem class! Unless there are lots of integers this is the way to go because you get Object Inspector integration for almost free (free as in little extra work hours).

If you do want to keep your list in it's current, highly efficient form (TList) then you're right, the way to go is to define your own property. Look into the Graphics.pas unit at how TPicture.DefineProperties is implemented because that's a very close match to what you need!

Idea: If you go the "DefineProperties" route you might want to look into RegisterComponentEditor because your Integer list will not be visible in Object Inspector!

Cosmin Prund
+5  A: 

Here's a minimal example with DefineBinaryProperty (written in D2007, no generics):

type
  TTestComponent = class(TComponent)
  private
    FList: TList;

    function GetValueCount: Integer;
    function GetValues(Index: Integer): Integer;
    procedure ReadValueList(Stream: TStream);
    procedure SetValues(Index: Integer; Value: Integer);
    procedure WriteValueList(Stream: TStream);
  protected
    procedure DefineProperties(Filer: TFiler); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    function AddValue(Value: Integer): Integer;
    procedure ClearValues;
    procedure DeleteValue(Index: Integer);

    property ValueCount: Integer read GetValueCount;
    property Values[Index: Integer]: Integer read GetValues write SetValues;
  end;

{ TTestComponent }

function TTestComponent.GetValueCount: Integer;
begin
  Result := FList.Count;
end;

function TTestComponent.GetValues(Index: Integer): Integer;
begin
  Result := Integer(FList[Index]);
end;

procedure TTestComponent.ReadValueList(Stream: TStream);
var
  Count, I, Value: Integer;
begin
  ClearValues;
  Stream.Read(Count, SizeOf(Count));
  for I := 0 to Count - 1 do
  begin
    Stream.Read(Value, SizeOf(Value));
    AddValue(Value);
  end;
end;

procedure TTestComponent.SetValues(Index: Integer; Value: Integer);
begin
  FList[Index] := Pointer(Value);
end;

procedure TTestComponent.WriteValueList(Stream: TStream);
var
  Count, I, Value: Integer;
begin
  Count := ValueCount;
  Stream.Write(Count, SizeOf(Count));
  for I := 0 to Count - 1 do
  begin
    Value := Values[I];
    Stream.Write(Value, SizeOf(Value));
  end;
end;

procedure TTestComponent.DefineProperties(Filer: TFiler);
begin
  inherited DefineProperties(Filer);
  Filer.DefineBinaryProperty('ValueList', ReadValueList, WriteValueList, ValueCount > 0);
end;

constructor TTestComponent.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FList := TList.Create;
  // add some values for testing
  AddValue(0);
  AddValue(1);
  AddValue(2);
end;

destructor TTestComponent.Destroy;
begin
  FList.Free;
  inherited Destroy;
end;

function TTestComponent.AddValue(Value: Integer): Integer;
begin
  Result := FList.Add(Pointer(Value));
end;

procedure TTestComponent.ClearValues;
begin
  FList.Clear;
end;

procedure TTestComponent.DeleteValue(Index: Integer);
begin
  FList.Delete(Index);
end;

and the .dfm looks like this:

  object TestComponent1: TTestComponent
    Left = 96
    Top = 56
    ValueList = {03000000000000000100000002000000}
  end
TOndrej
Thanks, this is just what I needed.
Mason Wheeler
+1 excellent solution @TOndrej
RRUZ