TPolymorphicList.
--------------------
What is it?

This Delphi 4 component holds a Polymorphic Persistent List of 
TPersistent descendants. That, of course, includes TComponents. 
I developed it as a helper component to save application runtime state. 

The problem:

I personally have allways found boring to store and retrieve every little detail 
of application runtime state to/from files. I divide runtime state two parts: model 
state and interface state. 

Interface state (toolbars state, forms layout, background collor etc) can be stored as 
properties of visual VCL components. This is easy to do using Delphi's built in component 
streaming capabilities (a.k.a the DFM files). 

Model state is normally contained in simple non visual objects (ex: TEmployee, TSpaceShip 
or whatever) that are created at runtime. 

To save both kinds of states to a single stream, we need to store both VCL state and runtime 
objects. VCL properties can be stored at runtime, using components like Pythoness's 
PFormSettings or RxLib's TFormStorage. You choose a set of properties of visual components 
at design time and they get saved to inifiles or registry. But these will not save 
runtime objects that even existed at design time. One solution is to use a TCollection 
to save the runtime objects as TCollectionItems. 
Unfortunately, TCollection descendents are complicated (boring) to implement. 

The answer:

TPolymorphicList is a non-visual component that can save runtime objects as a hidden 
property. All you have to do is include your objects in the list. Since the list is 
polymorphic, you can choose to save all your objects in a single TPolymorphicList or 
use multiple lists. 

TPolymorphicList overrides DefineProperties() to save the objects. 

Object requirements:

In order to save your objects in a TPolymorphicList, they must meet some requirements:
1) Objects must be a descendent of TPersistent. This is no big deal. 
Since TPersistent has no field, it uses the same amount of memory as a TObject.
2) Object state must me defined by the published properties. Public properties and 
array properties will not be saved. The same goes to non-registered (unknown) property types.
3) Must call RegisterClasses() in the initialization section of the unit that implements 
the object. Register all classes that you are going to put in the list or the load 
proccess will raise a 'class not found exception'.

Component Usage:

Component definition is:

  TPolymorphicList = class(TComponent)
    ...
  public
    Constructor Create(aOwner:TComponent); override;
    Destructor  Destroy;                   override;
    Procedure   Add(aObj:TPersistent);
    Procedure   Remove(aObj:TPersistent);
    procedure   Clear;
    Procedure   PickObjects(C:TPersistentClass;aList:TList); //removes objs from this list
    Procedure   SaveToStream(St:TStream);
    Procedure   ReadFromStream(St:TStream);
    Function    Edit:boolean; {dialog to edit this thing}
    Property    Count:integer read GetCount;
    Property    Items[index:integer]:TPersistent read GetItem;
  published
    Property  OwnObjects:boolean read fOwnObjects write fOwnObjects default FALSE;
    Property  OnCreatePersistent:TCreatePersistentEvent read fOnCreatePersistent write fOnCreatePersistent;
  end;

As you can see, it looks like a TList, except for:
1) Property OwnObjects:boolean - Specifies if objects should be disposed when 
TPolymorphicList.clear or TPolymorphicList.free are called. Default=FALSE 
(that is, someone will come to pickup objects after they are loaded). 

2) OnCreatePersistent event- TComponent.Create() is virtual, so calling a 
TComponentClass.Create() will call the correct TComponent descendant constructor. 
Unfortunately, the constructor TPersistent.Create is static. Calling TPersistentClass.Create 
allways calls TPersistent.Create, even if a TPersistent descendent redefines this method. 
In other words, TPolymorphicList cannot call the correct object constructor because 
it has no VMT entry.

I would like to have a class between TPersistent and TComponent, with no fields and 
a virtual constructor. But since there isn't, I used an event to allow the user (you) 
to call the correct Create method. In most cases, handling is simple:

procedure TForm1.PolyList1CreatePersistent(Sender: TObject;
  C: TPersistentClass; var aObj:TPersistent);
begin
  if C=TMyPersistent1 then aObj:=TMyPersistent1.Create
  else if C=TMyPersistent2 then aObj:=TMyPersistent2.Create;
  //no need to test for TMyComponent, since the correct 
  //TcomponentClass.Create() gets called automaticaly (it's virtual).
end;

if this event is not handled or aObj is not created, the TPersistent.Create is used. 
This can lead to problems if the object does important initialization during a it's 
own redefined Create. 

3) Procedures SaveToStream(St:TStream) and ReadFromStream(St:TStream) do what you are 
thinking. Use them to store your objects to files, database blob fields etc.

Know problems:

known problems in the current release (1.0):
1- Will not restore events correctly. In fact you will get a GPF if your object has an 
assigned event property .
2- Will not restore TWinControl components correctly. The problem here seams to be restoring 
the Parent. I tried to undestand classes.pas, but run out of patience.
3- Will not call the correct TPersistent descendent constructor automatically. 
I can't see how it can be done, since TPersistent.Create is static 
(if you have any ideas, I'd like to hear). 
You will have to handle the OnCreatePersistent event to call YourObject.Create.

Any help with these problems is welcome.

Availability:

Full source code is available for free. Of course, no warranty.. use at your own risk .. etc..
Support: None. No "newbie" questions, please. Full source code is available, so you can 
help yourself. However, I do accept - and welcome - corrections and additions to the component. 

Installation:

Install the component in PolyList.pas (create a new Package for it, if you want).

Omar Reis
email: omar@tecepe.com.br