Monday, November 06, 2006

How to persist a TPersistent?

In a recent project the end-user should be able to do some settings of a specific control. The control, an InstrumentMeter, has a lot of properties, and only some properties, like color, scale etc. had to be customizable by the end-user.

A very common way to let a user edit properties is to use a RTTI property inspector. The user can then edit properties in a Delphi way. I use the Devexpress RTTI inspector, but there are many alternatives outthere. The RTTI inspector object has a property to link the 'to be inspected control' called InspectedControl of type TPersistent.
Because users may only edit some of the properties I could not set the control there, because all the published properties would become editable.

So I decided to make a wrapper class for the control, which published only the necessary properties and 'links' them to the InstrumentMeter control.
(You can inherit a control and publish only those properties that you want to, but this works only inside of Delphi) 

I decided to inherit this class from TPersistent, so that I could set it in my RTTI Inspector.
The class looks somewhat like this:

type
TInstrument = class(TPersistent)
private
public

  //the InstrumentControl  
  property InstrumentControl : TInstrumentControl;
published
  //All enduser props
  property Kleur: TColor read FColor write SetColor(const Value : TColor);
  property ...
end;


Properties set by the user (for instance Kleur) must be saved to a database. I could of course extend a table with fields for all the properties, but having several types of instruments this would give a lot of extra fields.

Why not persist the TInstrument object?
Yeah great idea, stream the object into a blobfield of the database. This can be done easily with a TComponent class, but how do you do this with a TPersistent class?

A TComponent class could be streamed with the TStream WriteComponent method like this:

MyStream.WriteComponent(AComponent);

This stream then, could be saved into a Blobfield and later on read with the TStream ReadComponent method.

I realize that I should inherit from TComponent so that I would not have a problem streaming, but hey let's persist on the TPersistent for now.

How to save a TPersistent to a stream?
As far as I know now, this is not possible.
The only way to do this, as far as I know, is to use a DummyComponent (TComponent) with a property holding the TPersistent object, and then streaming both in the database. That works fine!

The code looks like this:

type
  TDummyObject = class(TComponent)
  private
    FPersistent: TPersistent;
  public
    //Property for holding my Persistent object
   property Persistent: TPersistent read FPersistent write FPersistent;
end;

procedure SavePersistentToStream(APersistent: TPersistent; AStream: TStream);
var
  DummyObject: TDummyObject;
begin
  DummyObject:= TDummyObject.Create(nil);
  try
    DummyObject.Persistent := APersistent;
    //write the component into the stream
    Stream.WriteComponent(DummyObject);
  finally
    DummyObject.Free;
  end;
end;

procedure LoadPersistentFromStream(APersistent: TPersistent; AStream: TStream);
var
  DummyObject: TDummyObject;
begin
  DummyObject:= TDummyObject.Create(nil);
  try
    DummyObject.Persistent := APersistent;
    AStream.ReadComponent(DummyObject);
  finally
    DummyObject.Free;
  end;
end;

Writing this I decided to inherit from TComponent anyway (why not?) but the question remains, are there other ways to persist a TPersistent?

3 comments:

Iman said...

Check out Hallvard's blog
http://hallvards.blogspot.com/2006/09/extended-class-rtti.html

Flávio said...

the property persistent on the component class must have published visibility...check this out!

Anonymous said...

The whole idea of streaming a relative lightweight tpersistent is NOT streaming all the added fields in TComponents.

Your solution might be a nice system for "just a quickie", but unfortunately it is not usable for large amounts