2

I am using the code below to set the properties using RTTI with Delphi 10.2 Tokyo in components created at runtime, everything works correctly because the property of the example is the TypeLine, because I can access it directly.

Componente_cc Is a variable that can be instantiated with any class, be it TLabel, TButton, TEdit... or any other. In the case below I'm instantiating it as being a TLine.

Var 
    Componente_cc: TControl;

    procedure TfrmPrincipal.AlteraPropriedades;
    begin
        if IsPublishedProp(Componente_cc, 'LineType') then
          SetPropValue(Componente_cc, 'LineType', 'Diagonal');
    end; 

However, I did not understand how to do when there is a sub-property, such as Stroke, it has Kind, Color, Cap, Dash, among others. How to change the values of these properties by using the SetPropValue() function. I have simplified the example code for a better understanding, but in the general context of my system I will need to use RTTI, of course changing the properties directly by the code would be simple, but I do need RTTI.

Anderson
  • 363
  • 1
  • 3
  • 10

1 Answers1

6

This is similar to your other RTTI issue, where you are accessing a control's TextSettings.Font property via RTTI. The same thing applies for any nested-property, like Stroke.Color, etc.

For each nested sub-property, you have to get the containing object, repeating as needed until you reach the desired sub-object, then you can get/set its property values as needed.

So, in this case, you have to use GetObjectProp() to get the Stroke property object, then you can use SetPropValue() to set that object's properties. For example:

uses
  ..., TypInfo;

var 
  Componente_cc: TControl;

procedure TfrmPrincipal.AlteraPropriedades;
var
  Stroke: TObject;
begin
  if IsPublishedProp(Componente_cc, 'Stroke') then
  begin
    Stroke := GetObjectProp(Componente_cc, 'Stroke');
    if Stroke <> nil then
      SetPropValue(Stroke, 'Color', ...);
  end;
end; 

Or, to avoid a double RTTI lookup of the named property:

uses
  ..., TypInfo;

var 
  Componente_cc: TControl;

procedure TfrmPrincipal.AlteraPropriedades;
var
  PropInfo: PPropInfo;
  Stroke: TObject;
begin
  PropInfo := GetPropInfo(Componente_cc, 'Stroke', [tkClass]);
  if PropInfo <> nil then
  begin
    Stroke := GetObjectProp(Componente_cc, PropInfo);
    if Stroke <> nil then
      SetPropValue(Stroke, 'Color', ...);
  end;
end; 

Note that a more powerful Enhanced RTTI was introduced in Delphi 2010 (this RTTI is not limited to just published properties, like old-style RTTI is), for example:

uses
  ..., System.Rtti;

var 
  Componente_cc: TControl;

procedure TfrmPrincipal.AlteraPropriedades;
var
  Ctx: TRttiContext;
  Prop: TRttiProperty;
  Stroke: TObject;
begin
  Ctx := TRttiContext.Create;

  Prop := Ctx.GetType(Componente_cc.ClassType).GetProperty('Stroke');
  if (Prop <> nil) and (Prop.PropertyType.TypeKind = tkClass) {and (Prop.Visibility = mvPublished)} then
  begin
    Stroke := Prop.GetValue(Componente_cc).AsObject;
    if Stroke <> nil then
    begin
      Prop := Ctx.GetType(Stroke.ClassType).GetProperty('Color');
      if (Prop <> nil) {and (Prop.Visibility = mvPublished)} then
        Prop.SetValue(Stroke, ...);
    end;
  end;
end; 

However, it is better to just access sub-properties directly once you have access to a higher-level object, for example:

uses
  ..., TypInfo;

var 
  Componente_cc: TControl;

procedure TfrmPrincipal.AlteraPropriedades;
var
  PropInfo: PPropInfo;
  Stroke: TStrokeBrush;
begin
  PropInfo := GetPropInfo(Componente_cc, 'Stroke', [tkClass]);
  if PropInfo <> nil then
  begin
    Stroke := GetObjectProp(Componente_cc, PropInfo, TStrokeBrush) as TStrokeBrush;
    if Stroke <> nil then
      Stroke.Color := ...; // <-- no RTTI needed!
  end;
end; 

Or:

uses
  ..., System.Rtti;

var 
  Componente_cc: TControl;

procedure TfrmPrincipal.AlteraPropriedades;
var
  Ctx: TRttiContext;
  Prop: TRttiProperty;
  Stroke: TStrokeBrush;
begin
  Ctx := TRttiContext.Create;

  Prop := Ctx.GetType(Componente_cc.ClassType).GetProperty('Stroke');
  if (Prop <> nil) and (Prop.PropertyType.TypeKind = tkClass) {and (Prop.Visibility = mvPublished)} then
  begin
    Stroke := Prop.GetValue(Componente_cc).AsObject as TStrokeBrush;
    if Stroke <> nil then
      Stroke.Color := ...; // <-- no RTTI needed!
  end;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Hello @Remy, when you say there is a more robust RTTI since 2010 refers to your second example? Accessing properties directly? – Anderson Aug 31 '17 at 18:35
  • I'm using RTTI because it was the only solution I could find, my problem is that the software in question resembles an IDE, the user adds controls and changes properties, resizes, and so on, so the components are being added to the form at runtime, they can be of any class and I'm trying to do a single function to add all types of components, another to get the properties and another to set the properties, so I created the object of type TControl and in it the classes, the problem is that in TControl I do not have direct access to all properties, so I used RTTI in it. – Anderson Aug 31 '17 at 18:37
  • @Anderson: Accessing properties directly has nothing to do with RTTI, that is just ordinary object-oriented programming. The use of `PPropInfo` is not new, it has always been available. The old-style RTTI implemented by the `System.TypInfo` unit has been replaced by [Enhanced RTTI implemented by the `System.Rtti` unit](http://docwiki.embarcadero.com/RADStudio/en/Working_with_RTTI_Index). – Remy Lebeau Aug 31 '17 at 19:43
  • I understand, thank you. Anyway worked like this, I will now improve the code and study the subject better. – Anderson Aug 31 '17 at 19:48