may 072014
 

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.

tmycomponent 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.

persistentprop 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.

 Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)