tags:

views:

263

answers:

2

Recently I found a piece of code that creates an instance of TButton from a string: 'TButton' was used as a parameter.

See "Is there a way to instantiate a class by its name in Delphi?"

I am trying to save published properties of any object to an XML file (which works fine), and lately I want to recreate these objects from the XML file. In this file is written which class is supposed to be created (for example TButton) and then follows a list of properties, which should be loaded into this run-time-created object.

The example above shows the way how to do it, but it does not work for the class of my own. See code below:

  TTripple=class (TPersistent)
    FFont:TFont;
  public
    constructor Create;
    Destructor Destroy;override;
  published
    property Font:TFont read FFont write  FFont;
  end;
var
  Form1: TForm1;


implementation

{$R *.dfm}

constructor TTripple.Create;
  begin
  inherited;
  FFont:=TFont.Create;
  end;


destructor TTripple.Destroy;
  begin
  FFont.Free;
  inherited;
  end;

procedure TForm1.FormCreate(Sender: TObject);
begin
RegisterClasses([TButton, TForm, TTripple]);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  CRef : TPersistentClass;
  APer : TPersistent;
begin
 // CRef := GetClass('TButton');
  CRef := GetClass('TTripple');
  if CRef<>nil then
  begin
     APer := TPersistent(TPersistentClass(CRef).Create);
     ShowMessage(APer.ClassName);  // shows TTripple, what is correct
     if APer is TTripple then (APer as TTripple).Font.Color:=90;

     /// Here  I get error message, because TTriple was not created... ?!?!?!

  end;
end;

I can not get through. The TTripple object is perhaps created, but its constructor is not used.

+5  A: 

The TRipple constructor isn't being called because it's not virtual.

When you're constructing an object from a class reference, the compiler doesn't know what the final class type is yet, so it can't assign the right constructor in code. All it knows is that it's descending from TPersistent, so it writes out code to call the constructor for TPersistent, which is TObject.Create. If you want to call the right constructor, you have to do it virtually.

There's already a virtual constructor defined for reading classes from a class name. It's defined in TComponent. Make TRipple descend from TComponent and override its virtual constructor (the one that takes an Owner as a parameter) and then your code will work.

Mason Wheeler
Thanx a lot, Mason. It works now.... Marvelous... :-)
lyborko
Glad to be able to help!
Mason Wheeler
+4  A: 

You may not want to use TComponent, and there is another way of doing this.

add a reference to your class

TTrippleClass = class of TTripple;

Then your buttonclick becomes :

procedure TForm1.Button1Click(Sender: TObject);
var
  CRef : TTrippleClass;
  APer : TPersistent;
begin
  CRef := TTrippleClass(GetClass('TTripple'));
  if CRef<>nil then
  begin
    APer := TTripple(TTrippleClass(CRef).Create);
    ShowMessage(APer.ClassName);  // shows TTripple, what is correct
    if APer is TTripple then (APer as TTripple).Font.Color:=90;
  end;
end;

Now you may want to have more than one Tripple type then create an custom ancestor.

TCustomTripple = class(TPersistent)
public
  constructor Create;virtual;
end;

TCustomTrippleClass = class of TCustomTripple;

TTripple = class(TCustomTripple)
strict private
  fFont : TFont;
public
  constructor Create;override;
  destructor Destroy;override;
  property Font : TFont read fFont;
end;


constructor TCustomTripple.Create;
begin
  inherited Create;
end;

constructor TTripple.Create;
begin
  inherited;
  fFont := TFont.Create;
end;

destructor TTripple.Destroy;
begin
  fFont.Free;
  inherited;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  CRef : TCustomTrippleClass;
  APer : TCustomTripple;
begin
  CRef := TCustomTrippleClass(GetClass('TTripple'));
  if CRef<>nil then
  begin
    APer := TCustomTripple(TCustomTrippleClass(CRef).Create);
    ShowMessage(APer.ClassName);  // shows TTripple, what is correct
    if APer is TTripple then (APer as TTripple).Font.Color:=90;
  end;
end;
Steve
Did you try and compile this? It won't work, because of the double declaration of TCustomTripple (as 'TPersistent' and 'class of TCustomTripple').
Ken White
I did, but then copied it (by hand) as I have Delphi on another machine to the one I was posting the answer on. I'll check it.
Steve
Thanks Ken, your first example works fine. I appreciate. In the second I could not complile that - exactly as you said. For my puropose Mason's answer is enough...
lyborko
Thanks Steve, of Course... ;-)
lyborko
+1 for demonstrating (in the first example, at least) that the constructor does **not** need to be virtual.
Rob Kennedy
Thanks Steve, now it works correctly. Thumbs up...
lyborko