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

2

7

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
...
Ambulant answered 20/9, 2020 at 19:57 Comment(0)
T
10

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.

Tuttifrutti answered 20/9, 2020 at 20:58 Comment(4)
great explanation! is there any standard solution to filter out the duplications and retieve only the "top level" properties?Carom
@Carom That depends on requirements. See also the other answer that suggests using GetDeclaredProperties. But it excludes all inherited properties and will return only properties declared in the reflected type. Also note that by reintroducing a property you don't override the original property, but you create new one in the derived type. This new property can be completely unrelated to the original one, if you specify a getter or setter.Tuttifrutti
@Carom Maybe you'll be fine using classic RTTI for enumerating published properties only.Tuttifrutti
thx! meanwhile the problem solved, turned out the the GetProperties array already ordered by class hierarchy. #65981214Carom
I
6

Would GetDeclaredProperties() be more useful?

The help states that the difference between GetDeclaredProperties() and GetProperties() is:

Use the GetDeclaredProperties method to obtain a list of all the properties that are declared in the reflected type. To obtain the list of all properties in the reflected type (including the inherited ones), use the GetProperties method instead.

Therefore I would think in your case GetProperties() is working as designed, and returning both properties and inherited properties - hence why some properties are appearing more than once.

Insolvency answered 21/9, 2020 at 0:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.