Search code examples
delphirttidelphi-10.4-sydney

Why are some properties repeated when TRttiContext.GetType is called on a VCL Control?


Why are some properties repeated (such as Action and Align) where others are not (AlignWithMargins) when TRttiContext.GetType is called on a VCL Control?

uses
  System.RTTI,
  System.Generics.Collections,
  System.Generics.Defaults;

//....

procedure TForm11.btnShowPropertiesClick(Sender: TObject);
var
  R: TRttiContext;
  Props: TArray<TRttiProperty>;
  Prop : TRttiProperty;
begin
  memo1.Clear;
  R := TRttiContext.Create;
  Props := R.GetType(Sender.ClassType).GetProperties;

  //Sort properties by name
  TArray.Sort<TRttiProperty>(props,
    TComparer<TRttiProperty>.Construct(
      function(const Left, Right: TRttiProperty): Integer
      begin
        result := CompareText(Left.Name, Right.Name);
      end
    )
  );

  for prop in Props do
  begin
    try
      Memo1.Lines.Add(
         Prop.Name + ' : ' +
         Prop.PropertyType.ToString + ' = ' +
         Prop.GetValue(Sender).ToString);
    except
      Memo1.Lines.Add(Prop.Name + ' generated an exception');
    end;
  end;
end;

Output

Action : TBasicAction = (empty)
Action : TBasicAction = (empty)
Align : TAlign = alNone
Align : TAlign = alNone
AlignDisabled : Boolean = False
AlignWithMargins : Boolean = False
Anchors : TAnchors = [akLeft,akTop]
Anchors : TAnchors = [akLeft,akTop]
BiDiMode : TBiDiMode = bdLeftToRight
BiDiMode : TBiDiMode = bdLeftToRight
...

Solution

  • If you would add Prop.Parent.Name to the loop that populates the memo you could have easily found out the cause:

    for prop in Props do
    begin
      try
        Memo1.Lines.Add(
           Prop.Parent.Name + '.' + { added }
           Prop.Name + ' : ' +
           Prop.PropertyType.ToString + ' = ' +
           Prop.GetValue(Sender).ToString);
      except
        Memo1.Lines.Add(Prop.Name + ' generated an exception');
      end;
    end;
    

    The code above produces:

    TButton.Action : TBasicAction = (empty)
    TControl.Action : TBasicAction = (empty)
    TControl.Align : TAlign = alNone
    TButton.Align : TAlign = alNone
    TWinControl.AlignDisabled : Boolean = False
    TControl.AlignWithMargins : Boolean = False
    TControl.Anchors : TAnchors = [akLeft,akTop]
    TButton.Anchors : TAnchors = [akLeft,akTop]
    TButton.BiDiMode : TBiDiMode = bdLeftToRight
    TControl.BiDiMode : TBiDiMode = bdLeftToRight
    ...

    Now you can clearly see that that GetProperties enumerates properties that were reintroduced in descendant class with higher visibility or altered order. This is typical for controls when TCustomMyControl defines SomeProperty with protected visibility and TMyControl reintroduces it at published level.

    You can try adding interposer class for TButton before the TForm11 declaration:

    type
      TButton = class(Vcl.StdCtrls.TButton)
      published
        property AlignWithMargins;
      end;
    

    The output would reflect the change. I added fully qualified name of property's declaring type (Prop.Parent.QualifiedName) to make it more obvious that TButton comes from my own unit.

    Vcl.StdCtrls.TButton.Action : TBasicAction = (empty)
    Vcl.Controls.TControl.Action : TBasicAction = (empty)
    Vcl.Controls.TControl.Align : TAlign = alNone
    Vcl.StdCtrls.TButton.Align : TAlign = alNone
    Vcl.Controls.TWinControl.AlignDisabled : Boolean = False
    Vcl.Controls.TControl.AlignWithMargins : Boolean = False
    Unit1.TButton.AlignWithMargins : Boolean = False
    Vcl.Controls.TControl.Anchors : TAnchors = [akLeft,akTop]
    Vcl.StdCtrls.TButton.Anchors : TAnchors = [akLeft,akTop]
    Vcl.StdCtrls.TButton.BiDiMode : TBiDiMode = bdLeftToRight
    Vcl.Controls.TControl.BiDiMode : TBiDiMode = bdLeftToRight
    ...

    This kind of behavior is not limited to controls. It can be observed on any class that reintroduces properties from an ancestor class.