//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.

To download click here

email: omar@tecepe.com.br