delphicollectionstcollectionitem

Can Delphi TCollectionItem have a TFmxObject in its properties?


I created TOwnedCollection / TCollectionItem pair which tries to keep a TFmxObject as one of its streaming properties. However, the TFmxObject (FMyObject) does not stream in code when saved.

TMyItem = class(TCollectionItem)
private
  FName: string;
  FMyObject: TMyObject;
public
  constructor Create(Collection: TCollection); overload; override;
  destructor Destroy; override;
published
  property Name: string read FName write FName;
  property MyObject: TMyObject read FMyObject write FMyObject;
end;

// TMyItems CLASS not shown as it is standard code

TMyObject = class(TFmxObject)
private var
  FObjectName: string;
published
  property ObjectName : string read FObjectName write FObjectName;
end;

///////////////////////////////////////////////////////
implementation

constructor TMyItem.Create(Collection: TCollection);
begin
  inherited Create(Collection);
  FMyObject := TMyObject.Create(nil); // note: TMyObject.Create(self) will not work
end;

destructor TMyItem.Destroy;
begin
  FMyObject.Free;
  inherited Destroy;
end;

Inside the streaming information, despite setting TMyObject.ObjectName, TMyObject is not streamed into MyItems (i.e. ObjectName = 'Object1' is not streamed)

MyItems = <
  item
    Name = 'Test1'
  end
  item
    Name = 'Test2'
  end>

Sample code:

var oNewMyItem := [....]MyItems.Add('Test1');
oNewMyItem.MyObject.ObjectName := 'Object1';  // no errors or AV but does not stream

I need MyObject to be TFmxObject as it is used elsewhere, but I need it to be "stream-able" in TMyItem's properties.

Thanks for any advice.


Solution

  • Since TMyItem owns the FMyObject object, but doesn't assign an Owner to it, you need to make TMyItem.Create() call FMyObject.SetSubComponent(true) to stream it properly:

    constructor TMyItem.Create(Collection: TCollection);
    begin
      inherited Create(Collection);
      // note: TMyObject.Create(self) will not work
      FMyObject := TMyObject.Create(nil); 
      FMyObject.SetSubComponent(True); // <-- add this
    end;
    

    Also, your MyObject property setter is writing directly to the FMyObject member. If anyone were to assign a TMyObject object to your property, it would overwrite that member leaking the original object that you create, and take ownership of the caller's object instead. Neither is correct behavior in this case. Your MyObject property instead needs a setter method that calls FMyObject.Assign(), eg:

    TMyItem = class(TCollectionItem)
    private
      ...
      FMyObject: TMyObject;
      procedure SetMyObject(AValue: TMyObject);
      ...
    published
      ...
      property MyObject: TMyObject read FMyObject write SetMyObject;
    end;
    
    procedure TMyItem.SetMyObject(AValue: TMyObject);
    begin
      FMyObject.Assign(AValue);
    end;