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!