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

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.


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.