Implementing TCollection

by Sep 25, 1997

 Technical Information Database

TI1647D.txt - Implementing TCollection

Category   :ActiveX/OLE/COM/ActiveForm
Platform   :All-32Bit
Product    :All32Bit,   

Description:

  This document is intended for those needing to descend from a class
that manages an array of lightweight persistent objects of the same
type.  The class that best accomplishes this is TCollection and
TCollectionItem.  For example, TCollection is used to manage Panels
in a TStatusBar, Columns in a TDBGrid, or Constraints in a TTable.
  
  This document begins with a discussion of the expected behavior of 
TCollection descendants, followed by a listing of the minimal steps
necessary to implement a TCollection descendant, a listing of
the component source, and finally some notes on design decisions
and ideas for expansion of your TCollection descendant component.
  

General Discussion
------------------
  To become familiar with the default behavior of TCollection, try
adding a TStatusBar component to a form, click the ellipses of the
Panels property, press the Add button of the "Editing Panels".
This last step adds a TStatusPanel to the editor.  Click on
on the TStatusPanel item in the editor and notice the change in
the object inspector.  Instead of seeing TStatusBar now you will
see StatusBar1.Panels[0] reflected in the Object Inspector.  
  
  There are three major players involved with collections.  A
collection item (TCollectionItem) descendant, a TCollection that
manages the list of TCollectionItems, and a component that contains
the TCollection as one of it's properties.  In our above example of 
TStatusBar, TStatusBar contains a descendant of TCollection called
TPanels and TPanels manages a list of TCollectionItem descendants
called TPanel.  Notice that each TCollectionItem contains one or
more properties; for instance, TPanels contains Alignment, Bevel, 
Style, Text, and Width properties.  This list changes depending on
the definition of your TCollectionItem descendant.

Creating a Minimal TCollection Implementation
---------------------------------------------
In a new unit you must first define three new descendant classes
from TCollectionItem, TCollection and a TComponent.

  TMyCollectionItem = class(TCollectionItem)

  TMyCollection = class(TCollection)

  TMyComponent = class(TComponent)

  To make TMyCollectionItem functional, you need to define
one or more properties to contain information to be tracked
by the collection mechanism.  The example defines a Text and
a MoreStuff integer property.  You will also need to override
the GetDisplayName method to supply the string shown for each
item in the collection property editor:

 TMyCollectionItem = class(TCollectionItem)
  private
    FText: string;
    FMoreStuff: LongInt;
    function GetDisplayName: string; override;
    procedure SetText(const Value: string);
    procedure SetMoreStuff(const Value: LongInt);
  published
    property Text: string read FText write SetText;
    property MoreStuff: LongInt 
      read FMoreStuff write SetMoreStuff;
  end;

  Next, define the TCollection descendant.  This class will 
keep track of the component the collection belongs to, 
override the GetOwner method to accomodate streaming, and 
manage an array of the previously defined TCollectionItem
descendants.  
  You will need to define a new static constructor.  The parameter
passed in this constructor is the reference to the component 
that contains the collection.  Also in the constructor you 
need to populate the ItemClass property with the class of your
TCollection item descendant. Note: ItemClass returns the class 
(descended from TCollectionItem) to which the items in the 
collection belong. 

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

  Finally, define the component that will contain the collection.
The component will contain a property descended from the 
TCollection type defined previously.  The TCollection property
will need a private field, an access method to the private field,
and storage allocated in the constructor and freed in the
destructor.  

Note: See The Developers Guide for more information on creating 
custom components.


  TMyComponent = class(TComponent)
  private
    FItems: TMyCollection;
    procedure SetItems(Value: TMyCollection);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Items: TMyCollection 
      read FItems write SetItems;
  end;

Complete Unit Listing
---------------------
unit Collec1;

interface

//  Note: TCollection and TCollectionItem are defined in Classes.Pas.

uses Classes;

type

  TMyComponent = class;

  TMyCollectionItem = class(TCollectionItem)
  private
    FText: string;
    FMoreStuff: LongInt;
    function GetDisplayName: string; override;
    procedure SetText(const Value: string);
    procedure SetMoreStuff(const Value: LongInt);
  public
  published
    property Text: string read FText write SetText;
    property MoreStuff: LongInt read FMoreStuff write SetMoreStuff;
  end;

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

  TMyComponent = class(TComponent)
  private
    FItems: TMyCollection;
    procedure SetItems(Value: TMyCollection);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property Items: TMyCollection read FItems write SetItems;
  end;

procedure Register;

implementation

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


{ TMyCollectionItem }


// Note: Inherited default behavior of GetDisplayName is to 
// return the classname.

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

procedure TMyCollectionItem.SetText(const Value: string);
begin
  if FText <> Value then
    FText := Value;
end;

procedure TMyCollectionItem.SetMoreStuff(const Value: LongInt);
begin
  if FMoreStuff <> Value then
    FMoreStuff:= Value;
end;


{ TMyCollection }

constructor TMyCollection.Create(MyComponent: TMyComponent);
begin
  inherited Create(TMyCollectionItem);
  FMyComponent := MyComponent;
end;

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

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

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

// Note: You must override GetOwner in Delphi 3.x to get
// correct streaming behavior.
function TMyCollection.GetOwner: TPersistent;
begin
  Result := FMyComponent;
end;


{ TMyComponent }

constructor TMyComponent.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FItems := TMyCollection.Create(Self);
end;

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

procedure TMyComponent.SetItems(Value: TMyCollection);
begin
  FItems.Assign(Value);
end;

end.
{--------------------------------------------------------------------}


Notes
-----

  In this minimal example we didn't override the Assign method for
the TCollectionItem, but this method should have further support.
Here's an example of how you might implement Assign in the above 
project:

procedure TMyCollectionItem.Assign(Source: TPersistent);
begin
  if Source is TMyCollectionItem then
  begin
    Text := TMyCollectionItem(Source).Text;
    MoreStuff := TMyCollectionItem(Source).MoreStuff;
    Exit;
  end;
  inherited Assign(Source);
end;

  Also not included in the above project is the logic needed to 
notify the TCollection class when one of it's contained items has
changed.  This could be particularly important in a visual control
such as TStatusBar.  TCollection supplies a virtual Update method
for handling this behavior.  See TStatusBar or THeaderControl
in sourcevclcommctrls.pas for further examples.



Reference:
 

4/2/99 3:00:45 PM

Article originally contributed by