Buenas
Cuando diseñamos componentes y necesitamos tener una lista de algún objeto y queremos que ésta se muestre en el inspector de objetos, una buena solución es recurrir a las clases TCollection
y TCollectionItem
. Muchos componentes usan estas clases para mostrarnos sus propiedades, como por ejemplo el tan usado TDBGrid (su propiedad Columns es una TCollection y cada una de las columnas, TCollectionItem).
La verdad es que es sencillo de implementar. Veamos un pequeño ejemplo.
unit MyComponent; interface uses Classes; 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 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; TMyComponent = class(TComponent) private FCollectionProp: TMyCollection; procedure SetCollectionProp(const Value: TMyCollection); public constructor Create(AOwner: TComponent); override; destructor Destroy; override; published property CollectionProp: TMyCollection read FCollectionProp write SetCollectionProp; end; procedure Register; implementation uses SysUtils; procedure Register; begin RegisterComponents('Samples', [TMyComponent]); end; { TMyCollection } function TMyCollection.Add: TMyCollectionItem; begin Result := TMyCollectionItem(inherited Add); end; function TMyCollection.GetItem(Index: Integer): TMyCollectionItem; begin Result := TMyCollectionItem(inherited GetItem(Index)); 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; { TMyCollectionItem } procedure TMyCollectionItem.Assign(Source: TPersistent); begin inherited; if Source is TMyCollectionItem then FStringProp := TMyCollectionItem(Source).FStringProp; end; function TMyCollectionItem.GetDisplayName: String; begin Result := Format(ClassName + ' %d',[Index]); end; { TMyComponent } constructor TMyComponent.Create(AOwner: TComponent); begin inherited; FCollectionProp := TMyCollection.Create(TMyCollectionItem); end; destructor TMyComponent.Destroy; begin if Assigned(FCollectionProp) then FreeAndNil(FCollectionProp); inherited; end; procedure TMyComponent.SetCollectionProp(const Value: TMyCollection); begin FCollectionProp.Assign(Value); end; end.
Con estas pocas lineas de código, conseguimos un resultado cómo el que podemos ver en la imagen de la izquierda .
Básicamente lo que hemos hecho es crear una clase que descienda de TCollectionItem
a la que le hemos añadido la propiedad StringProp
.
También creamos una clase que descienda de TCollection
. Realmente esta clase se hace para sobreescribir algunos métodos de TCollection
para que, en lugar de devolver un TCollectionItem
y tengamos que hacer casteos, devuelva directamente un item de nuestra clase, es decir, un TMyCollectionItem
.
Para terminar el código, creamos una última clase que será el componente propiamente dicho con la propiedad de tipo TCollection
.
Hasta aquí todo normal y muy sencillo, pero… ¿qué pasa si lo que queremos es que nuestra colección «cuelgue» de otra propiedad de nuestro componente la cual deriva de TPersistent
? Veamos en la imagen siguiente lo que queremos conseguir.
En este caso, lo primero que pensamos es hacer una propiedad (llamada PersistentProp
) de tipo TPersistent
de donde hacer «colgar» nuestra TCollection
(como vemos en la imagen).
Al hacer ésto, nos encontramos con la sorpresa de que, si bien podemos ver nuestra propiedad en el inspector, no podemos acceder al editor de la colección. Esto es debido a que la colección necesita saber su owner y, la implementación del método GetOwner
es muy distinta en TPersistent
y TComponent
(en la primera simplemente devuelve un nil
, mientras que en la segunda devuelve el Owner.
Para solucionar el pequeño inconveniente, simplemente tenemos que decirle tanto a la propiedad TPersistent como a nuestra TCollection, cuál es su owner, y eso lo haremos en el constructor de la clase. Así pues, el código de nuestro componente quedará como sigue:
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 constructor Create(AOwner: TPersistent); destructor Destroy; override; procedure Assign(Source: TPersistent); 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 inherited; if Source is TMyCollectionItem then FStringProp := TMyCollectionItem(Source).FStringProp; end; function TMyCollectionItem.GetDisplayName: String; begin Result := Format(ClassName + ' %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 inherited; if Source is TMyPersistent then CollectionProp := TMyPersistent(Source).FCollectionProp; 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.
Fijaros que los constructores de la propiedad TPersistent
y de la TCollection
ahora reciben un parámetro, el Owner
. Además hemos sobreescrito el método GetOwner
tanto de TMyPersistent
como de TMyCollection
para que devuelve el owner de la clase.
Con ésto ya tendremos funcionando nuestro componente con el comportamiento deseado.
Para mejorar o simplificar el código, existe ya una colección con owner, la TOwnedCollection
, con lo que nuestra TCollection
podría quedar tan simple como ésto
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;
Pues nada más, espero que os haya servido el ejemplo. Como siempre, podéis descargaros el ejemplo des de aquí.
Saludos.