Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I create an instance from a string that provides the class name?

Tags:

delphi

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.

like image 236
lyborko Avatar asked Dec 29 '22 08:12

lyborko


2 Answers

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.

like image 125
Mason Wheeler Avatar answered Mar 16 '23 00:03

Mason Wheeler


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;
like image 21
Steve Avatar answered Mar 15 '23 22:03

Steve