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:

Abdul Alhazred said...

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

Flávio Granero 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

Use an image as your UIBarButtonItem

Using an image as your UIBarButtonItem in your navigationcontroller bar can only be achieved by using a common UIButton as the BarButtonItem...