1. Tech

Your suggestion is on its way!

An email with a link to:

http://delphi.about.com/library/bluc/text/uc083101d.htm

was emailed to:

Thanks for sharing About.com with others!

RTL reference|Glossary|Tips/Tricks|FREE App/VCL|Best'O'Net|Books|Link To
 
Creating Custom Delphi Components, Part II
Page 4: Collections
 More of this Feature
• Page 1: Component references
• Page 2: Sets
• Page 3: Binary properties
• Page 5: Sub-properties

• Download Demo Projects
 Join the Discussion
"Post your questions, concerns, views and comments to this article..."
Discuss!
 Related Resources
• Custom VCL development
• VCL with source
• Third party VCL
• VCL using

   Collections
As we progress through this article we cover component properties of more complexity. Collections are one of the most complex "standard" Delphi property types. If you drop a TDBGrid onto a form and look at its properties in the Object Inspector, you will see a property named "Columns".

Columns is a collection property, when you click on the […] button you will see a small window pop up. This window is the standard property editor for TCollection properties (and descendents of TCollection).

columns editor

Whenever you click the "New" button you see a new item added (a TColumn item), clicking on that item will select it into the Object Inspector so that you can alter its properties / events. How is this done ?

The Columns property descends from TCollection. TCollection is similar to an array, which contains a list of TCollectionItem's. Because TCollection is descended from TPersistent it is able to stream this list of items, similarly, TCollectionItem is also descended from TPersistent and can also stream its properties. So what we have is an array-like item capable of streaming all of its items and their properties.

The first thing to do when creating our own structure based on TCollection / TCollectionItem is to define our CollectionItem.

(See OurCollection.pas)

type
  TOurCollectionItem = class(TCollectionItem)
  private
    FSomeValue : String;
  protected
    function GetDisplayName : String; override;
  public
    procedure Assign(Source: TPersistent); override;
  published
    property SomeValue : String
      read FSomeValue
      write FSomeValue;
  end;

What we have done here is to create a descendent of TCollectionItem. We have added a token property called "SomeValue", overridden the GetDisplayName function (to alter the text that is shown in the default editor), and finally overridden the Assign method in order to allow TOurCollectionItem to be assigned to another TOurCollectionItem. If we omit the final step then the Assign method of our Collection class will not work !

procedure TOurCollectionItem.Assign(Source: TPersistent);
begin
  if Source is TOurCollectionItem then
    SomeValue := TOurCollectionItem(Source).SomeValue
  else
    inherited; //raises an exception
end;

function TOurCollectionItem.GetDisplayName: String;
begin
  Result := Format('Item %d',[Index]);
end;

The implementation of TOurCollection is much more complex, and requires us to do quite a bit of work.

  TOurCollection = class(TCollection)
  private
    FOwner : TComponent;
  protected
    function GetOwner : TPersistent; override;
    function GetItem(Index: Integer): TOurCollectionItem;
    procedure SetItem(Index: Integer; Value: 
      TOurCollectionItem);
    procedure Update(Item: TOurCollectionItem); 
  public
    constructor Create(AOwner : TComponent); 
    
    function Add : TOurCollectionItem;
    function Insert(Index: Integer): TOurCollectionItem;

    property Items[Index: Integer]: TOurCollectionItem
      read GetItem
      write SetItem;
  end;

There are a number of items to cover based on the above class declaration, so we shall start from the top and cover each in turn.

GetOwner is a virtual method introduced in TPersistent. This needs to be overridden as the default code for this method returns Nil. In our implementation we alter the constructor to receive only one parameter (AOwner : TComponent). We store this parameter in FOwner, which is then passed as the result of GetOwner (TComponent descends from TPersistent, so is therefore a valid result type).

constructor TOurCollection.Create(AOwner: TComponent);
begin
  inherited Create(TOurCollectionItem);
  FOwner := AOwner;
end;

function TOurCollection.GetOwner: TPersistent;
begin
  Result := FOwner;
end;

Not only does Create store the owner (which is required for the Object Inspector to work correctly), it also tells Delphi what class our CollectionItem is by calling "inherited Create(TOurCollectionItem)".

GetItem / SetItem are declared the same way as they are in TCollection, but instead of working on TCollectionItem they work on our new class TOurCollectionItem. These are used in our "Items" property later on.

Update as above is a straight forward replacement of the original, working on our new CollectionItem class instead.

Add / Insert are both responsible for adding items to the list, these have both been replaced to return objects of the appropriate class.

Finally, an "Items" property is introduced to replace the original Items property, again so that we are returned a result of TOurCollectionItem rather than TCollectionItem saving us the unnecessary problem of typecasting the result each time.

Finally an example of implanting this property type in a component of our own.

TCollectionComponent = class(TComponent)
  private
    FOurCollection : TOurCollection;
    procedure SetOurCollection(const Value: 
      TOurCollection);
  public
    constructor Create(AOwner : TComponent); override;
    destructor Destroy; override;
  published
    property OurCollection : TOurCollection
      read FOurCollection
      write SetOurCollection;
  end;

It is as simple as that. Once our TCollection class is written all of the hard work is done. Our constructor creates the collection class, the destructor destroys it, and SetOurCollection does this.

constructor TCollectionComponent.Create(AOwner: TComponent);
begin
  inherited;
  FOurCollection := TOurCollection.Create(Self);
end;

destructor TCollectionComponent.Destroy;
begin
  FOurCollection.Free;
  inherited;
end;

procedure TCollectionComponent.SetOurCollection(
  const Value: TOurCollection);
begin
  FOurCollection.Assign(Value);
end;

As mentioned before, the (Self) passed to the TOurCollectionItem.Create is stored in TOurCollection's FOwner variable, which is passed as the result of GetOwner. A point to note here is that in SetOurCollection we do not set FOurCollection := value as you are replacing the object (objects are simply pointers), we Assign our property to the value.

Later versions of Delphi make this simpler still. Rather than having to override GetOwner in our Collection class, we can now derive our Collection from TOwnedCollection instead. TOwnedCollection is a wrapper for TCollection with this work done for us.

Next page > Sub-properties > Page 1, 2, 3, 4, 5

Creating Custom Delphi Components >>
>> Part III.

All graphics (if any) in this feature created by Peter Morris.

 More Delphi
· Learn another routine every day - RTL Quick Reference.
· Download free source code applications and components.
· Talk about Delphi Programming, real time.
· Link to the Delphi Programming site from your Web pages.
· Tutorials, articles, tech. tips by date: 2001|2000|1999|1998 or by TOPIC.
· NEXT ARTICLE: Lookup! - DB/15.
Chapter fifteen of the free Delphi Database Course for beginners. See how to use lookup fields in Delphi to achieve faster, better and safer data editing. Also, find how to create a new field for a dataset and discuss some of the key lookup properties. Plus, take a look at how to place a combo box inside a DBGrid.
 Stay informed with all new and interesting things about Delphi (for free).
Subscribe to the Newsletter
Name
Email

 Got some code to share? Got a question? Need some help?

©2014 About.com. All rights reserved.