Collection editor does not open for a TCollection property in a TPersistent property
Asked Answered
G

1

2

I've got my custom collection property which is working great when it is a direct member of my component.

But I want to move the collection property to a TPersistent propery within my component. And now comes the problem, it doesn't work: double clicking on the collection property in the object inspector normally opens the collection editor, but it does not anymore.

Fist of all - what should I pass to the contructor of the TPersistent property?

TMyCollection = class(TCollection)
  constructor Create(AOwner: TComponent); // TMyCollection constuctor
  ...

I can't pass Self, so should I pass my persistent owner?

constructor TMyPersistent.Create(AOwner: TComponent);
begin
  inherited Create;
  fOwner := AOwner;
  fMyCollection := TMyCollection.Create(AOwner); // hmmm... doesn't make sense
end;

I think I'm missing something. If more code is needed just please comment this post.

Da visualizationz

Grandparent answered 8/8, 2011 at 9:55 Comment(15)
A TCollection's constructor does not need an owner, but a TCollectionItemClass.Antilles
Hmmm, but it's working well when I call it from component. So i assume that my TCollection and TCollectionItem is good.Grandparent
@Antilles This is the answer to the question. Why don't you write it as such. If you don't write an answer, then JustMe can't accept it!Kerman
"It doesn't work" is too vague to be meaningful. What exactly happens? Does it crash, and if so, with what error message, or doesn't it compile, and if so, with what error message?Isolde
@Rudy Nothing happens after clicking on TCollection to edit. After debugging it's an AVGrandparent
Perhaps this answer may help: https://mcmap.net/q/1482585/-using-townedcollection-descendant-in-delphi.Isolde
@Rudy Thanks for the link but it's not the TCollection inside TPersistent classGrandparent
@JustMe: that doesn't matter.Isolde
@Rudy So maybe I'm doing something wrong when I add TPersistent to component? This is how I'm doing this: codeviewer.org/view/code:1d62 (ADDED comments)Grandparent
@JustMe: I don't get it, what's the purpose of TMypersistent anyway ?Darondarooge
@Darondarooge to have en expandable properties of course :-)Grandparent
+1 Now the question is clear, it ís a really good question!Antilles
Read the text after the screenshot on page 5 of the tutorial. This answers your question.Altricial
@Stefan That's more a workaround, rather than an answer. See my answer below why. But you are right that OP obviously didn't read the whole tutorial. ;)Antilles
@NGLN: The original problem was: Why does the OI not work on that property. And reading the text I mentioned answers that. Imo that whole what owner should that collection have does nothing to that original problem.Altricial
A
10

A TCollection's constructor does not need a TComponent, but a TCollectionItemClass.

Your collection now being a member of a TPersistent property instead of being a direct member of the component makes no difference for the constructor.


Update

What dóes differ is the ownership, but then at the TPersistent level, which should be managed by a correct implementation of GetOwner:

GetOwner returns the owner of an object. GetOwner is used by the GetNamePath method to find the owner of a persistent object. GetNamePath and GetOwner are introduced in TPersistent so descendants such as collections can appear in the Object Inspector.

You have to tell the IDE that your TCollection property is owned by the TPersistent property, which in turn is owned by the component.

The tutorial you are using has several errors regarding this implementation:

  • The owner of the collection is declared as TComponent, which should be TPersistent,
  • GetOwner is not implemented for the TPersistent property class, and
  • The fix shown at the end of the tutorial, stating that the TPersistent property should inherit from TComponent instead, is plain wrong; or more nicely said: is rather a workaround for not implementing GetOwner.

This is how it should look like:

unit MyComponent;

interface

uses
  Classes, SysUtils;

type
  TMyCollectionItem = class(TCollectionItem)
  private
    FStringProp: String;
  protected
    function GetDisplayName: String; override;
  public
    procedure Assign(Source: TPersistent); override;
  published
    property StringProp: String read FStringProp write FStringProp;
  end;

  TMyCollection = class(TCollection)
  private
    FOwner: TPersistent;
    function GetItem(Index: Integer): TMyCollectionItem;
    procedure SetItem(Index: Integer; Value: TMyCollectionItem);
  protected
    function GetOwner: TPersistent; override;
  public
    constructor Create(AOwner: TPersistent);
    function Add: TMyCollectionItem;
    function Insert(Index: Integer): TMyCollectionItem;
    property Items[Index: Integer]: TMyCollectionItem read GetItem
      write SetItem;
  end;

  TMyPersistent = class(TPersistent)
  private
    FOwner: TPersistent;
    FCollectionProp: TMyCollection;
    procedure SetCollectionProp(Value: TMyCollection);
  protected
    function GetOwner: TPersistent; override;
  public
    procedure Assign(Source: TPersistent); override;
    constructor Create(AOwner: TPersistent);
    destructor Destroy; override;
  published
    property CollectionProp: TMyCollection read FCollectionProp
      write SetCollectionProp;
  end;

  TMyComponent = class(TComponent)
  private
    FPersistentProp: TMyPersistent;
    procedure SetPersistentProp(Value: TMyPersistent);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property PersistentProp: TMyPersistent read FPersistentProp
      write SetPersistentProp;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TMyComponent]);
end;

{ TMyCollectionItem }

procedure TMyCollectionItem.Assign(Source: TPersistent);
begin
  if Source is TMyCollectionItem then
    FStringProp := TMyCollectionItem(Source).FStringProp
  else
    inherited Assign(Source);
end;

function TMyCollectionItem.GetDisplayName: String;
begin
  Result := Format('Item %d',[Index]);
end;

{ TMyCollection }

function TMyCollection.Add: TMyCollectionItem;
begin
  Result := TMyCollectionItem(inherited Add);
end;

constructor TMyCollection.Create(AOwner: TPersistent);
begin
  inherited Create(TMyCollectionItem);
  FOwner := AOwner;
end;

function TMyCollection.GetItem(Index: Integer): TMyCollectionItem;
begin
  Result := TMyCollectionItem(inherited GetItem(Index));
end;

function TMyCollection.GetOwner: TPersistent;
begin
  Result := FOwner;
end;

function TMyCollection.Insert(Index: Integer): TMyCollectionItem;
begin
  Result := TMyCollectionItem(inherited Insert(Index));
end;

procedure TMyCollection.SetItem(Index: Integer; Value: TMyCollectionItem);
begin
  inherited SetItem(Index, Value);
end;

{ TMyPersistent }

procedure TMyPersistent.Assign(Source: TPersistent);
begin
  if Source is TMyPersistent then
    CollectionProp := TMyPersistent(Source).FCollectionProp
  else
    inherited Assign(Source);
end;

constructor TMyPersistent.Create(AOwner: TPersistent);
begin
  inherited Create;
  FOwner := AOwner;
  FCollectionProp := TMyCollection.Create(Self);
end;

destructor TMyPersistent.Destroy;
begin
  FCollectionProp.Free;
  inherited Destroy;
end;

function TMyPersistent.GetOwner: TPersistent;
begin
  Result := FOwner;
end;

procedure TMyPersistent.SetCollectionProp(Value: TMyCollection);
begin
  FCollectionProp.Assign(Value);
end;

{ TMyComponent }

constructor TMyComponent.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FPersistentProp := TMyPersistent.Create(Self);
end;

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

procedure TMyComponent.SetPersistentProp(Value: TMyPersistent);
begin
  FPersistentProp.Assign(Value);
end;

end.

But may I say that you can also inherit from TOwnedCollection, which makes the use and the declaration of TMyCollection much simpler:

  TMyCollection = class(TOwnedCollection)
  private
    function GetItem(Index: Integer): TMyCollectionItem;
    procedure SetItem(Index: Integer; Value: TMyCollectionItem);
  public
    function Add: TMyCollectionItem;
    function Insert(Index: Integer): TMyCollectionItem;
    property Items[Index: Integer]: TMyCollectionItem read GetItem
      write SetItem;
  end;
Antilles answered 8/8, 2011 at 10:20 Comment(5)
I was doing exactly like in: delphi.about.com/library/bluc/text/uc083101e.htm but those examples after compiling doesn't work either.Grandparent
Page 4 of that tutorial declares an own TOurCollection type which has indeed a constructor with an Owner parameter, but notice the inherited Create(TOurCollectionItem); within that constructor.Antilles
Yes, I do the same, but problem must be somewhere else because this example doesn't pop up the collection editor after clicking on the edit when Collection is in the TPersistent class. The "ExpandingComponent.pas"Grandparent
There is a bug in TMyPersistent.Assign. Instead of assigning directly to FCollectionProp (thus replacing the actual object) you should just use FCollectionProp.Assign(Source).Lomond
@Lomond Good remark! Fixed it, now it's delegated to the setter.Antilles

© 2022 - 2024 — McMap. All rights reserved.