Was sind Collections?

Nun, alle von Euch haben schon Collections verwendet - vermutlich ohne es zu wissen :) Jedes Property im Object Inspector, welches bei einem Doppelklick darauf eine Liste ffnet, in welcher man Elemente editieren kann, ist eine Ableitung von TCollection. z.B. Spalendefintion der Gride, Felderdefinitionen von TTable, Listview-Columns etc.

Collections kann man am ehesten mit TList bzw. TObjectList vergleichen. Es handelt sich um eine Liste, welche als Eintrge weitere Klassen verwendet. Ein Collection-Item kann auch wieder beliebig viele Subcollections haben, was das ganze definitiv unübersichtlich macht ;-)

"Für was brauch ich Collections, wenn ich doch schon Listen habe?", fragt Ihr Euch vielleicht. Nun, der Gag an den Collections ist, dass sie Component-Streaming unterstützen, was heisst, dass Ihr vollen Designtime-Support darin habt. Die Collection kümmert sich entsprechend um das Speichern und Laden der Collection-Items ohne, das Ihr nur eine Zeile Load-/Save-Code schreiben müsst. Ist doch noch ganz handlich, nicht? :) Am meisten werden Collections entsprechend fr Komponenten gebraucht.

Wie implementiert man Collections?

Es ist gar nicht mal so schwierig eigene Collections zu definieren. Als erstes braucht Ihr eine Ableitung von TCollection. Darin beschreibt Ihr die Methode GetOwner() und evtl. den Constructor Create() für Eurem eigenen Initialisierungskrempel. Optional könnt Ihr in Eurer Ableitung auch noch Add(), Assign() und Update() berschreiben, damit es komfortabler wird.

Die Collections halten Eintrge des Typs TCollectionItem. D.h., um Eure eigenen Daten in den Eintrgen zu speichern, müsst Ihr noch schnell eine Ableitung von TCollectionItem machen, in welcher Ihr alle properties, welche gespeichert/geladen werden sollen, als published Properties eintragt. In dem Ihr noch die Methode GetDisplayName() berschreibt, könnt Ihr noch den Anzeigetext im Editor (pro Item) selbst bestimmen.

Das ganze in einem Beispiel:

interface

type

  { TMyCollection beinhaltet eine Liste von TMyCollectionItem's, welche
    vom Designer autom. geladen und gespeichert werden }
  TMyCollectionItem = class;

  TMyCollection = class(TCollection)
  private
    FOwner: TPersistent;
    function GetItem(Index: Integer): TMyCollectionItem;
    procedure SetItem(Index: Integer; Value: TMyCollectionItem);
  protected
    function GetOwner: TPersistent; override;
    procedure Update(Item: TCollectionItem); override;
  public
    constructor Create(AOwner: TPersistent);
    function Add: TMyCollectionItem;
    property Items[Index: Integer]: TMyCollectionItem read GetItem 
                                                      write SetItem; default;
  end;
  
  TMyCollectionItem = class(TCollectionItem)
  private
    FCaption: string;
    FMyDate: TDateTime;
    FMyString: string;
    FOnDoSomething: TNotifyEvent;
  protected
    function GetDisplayName: string; override;
  public
    constructor Create(Collection: TCollection); override;
    
    procedure DoSomething; virtual; // Example method
  published
    { Property examples }
    property Caption: string read FCaption write FCaption;
    property MyDate: TDateTime read FMyDate write FMyDate;
    property MyString: string read FMyString write FMyString;
    { Event example}
    property OnDoSomething: TNotifyEvent read FOnDoSomething
                                         write FOnDoSomething;
  end;
  
implementation

{ TMyCollection }
  
constructor TMyCollection.Create(AOwner: TPersistent);
begin
  inherited Create(TMyCollectionItem);
  { Hier wird der Owner gesetzte, damit das Streaming funtzt. In Eurer
    Ableigung werdet Ihr wohl kaum TPersistent sondern Eure
    "Mutterklasse" verwenden. }
  FOwner := AOwner;
end;

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.GetOwner: TPersistent;
begin
  Result := FOwner;
end;

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

{ TMyCollectionItem }

constructor TMyCollectionItem.Create(Collection: TCollection);
begin
  FCaption := '';
  FMyDate := 0;
  FMyString := '';
  FOnDoSomething := nil;
end;

function TMyCollectionItem.GetDisplayName: string;
begin
  Result := Caption;
  if Result = '' then Result := inherited GetDisplayName;
end;

procedure TMyCollectionItem.DoSomething;
begin
  { Nur ein Beispiel }
  if assigned(FOnDoSomething) then
    FOnDoSomething(self);
end;

Wie Ihr seht, ist es gar nicht so eine Hexerei. Im TMyCollectionItem habe noch ein paar Beispiele eingebaut: GetDisplayText, Properties, Events und eine Methode, welche das Event at Runtime auslösen kann. Die TMyCollection kann man nun z.B. in einer eigenen Komponente verwenden. Sobald man diese Komponente auch in ein Package aufnimmt, hat man auch zur Designtime Zugriff auf die Collection. Happy collecting!