为什么在 VCL 控件上调用 TRttiContext.GetType 时某些属性会重复?

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

为什么在 VCL 控件上调用 TRttiContext.GetType 时,某些属性会重复(例如 ActionAlign)而其他属性不会重复(AlignWithMargins)?

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;

输出

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
...

如果您将 Prop.Parent.Name 添加到填充备忘录的循环中,您可以轻松找出原因:

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;

上面的代码产生:

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
...

现在您可以清楚地看到 GetProperties 枚举了在后代 class 中重新引入的具有更高可见性或更改顺序的属性。当 TCustomMyControl 定义具有 protected 可见性的 SomeProperty 并且 TMyControlpublished 级别重新引入它时,这是典型的控件。

您可以尝试在 TForm11 声明之前为 TButton 添加插入器 class:

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

输出将反映更改。我添加了 属性 的声明类型 (Prop.Parent.QualifiedName) 的完全限定名称,以便更明显地表明 TButton 来自我自己的单位。

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
...

这种行为不仅限于控件。可以在任何 class 上观察到从祖先 class.

重新引入属性

GetDeclaredProperties() 会更有用吗?

help 指出 GetDeclaredProperties() 和 GetProperties() 之间的区别是:

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.

因此我认为在您的情况下 GetProperties() 正在按设计工作,并返回属性和继承的属性 - 因此某些属性出现不止一次。