使用 RTTI 时,我们如何获取和设置更深层次的子属性?

Posted

技术标签:

【中文标题】使用 RTTI 时,我们如何获取和设置更深层次的子属性?【英文标题】:When using RTTI how can we get and set deeper level sub-properties? 【发布时间】:2015-09-26 15:15:10 【问题描述】:

概述

感谢之前有人问过几个类似的问题:

Get/Set sub properties ussing RTTI Get a sub property of a component in Delphi using RTTI how i can set the value of a nested property using the RTTI How can I set/get property value through RTTI for compex things like TStringGrid.Cells?

但是,我没有进一步了解 RTTI 如何准确地满足我的需求。

我也花了很多时间和精力来写这个问题,所以我希望它不会被关闭:)

工作示例

下面有一些程序可以将组件的属性名称、值和类型输出到 TStrings 列表。原始源不是我的,我只是对其进行了一些小改动,整理了代码并将它们放入一些整洁的可重用程序中:


下面会输出属性名,如:

颜色 双缓冲 已启用 身高 宽度
procedure GetComponentPropertyNames(Component: TComponent; OutList: TStrings);
var
  I: Integer;
  Count, Size: Integer;
  PropList: PPropList;
  PropInfo: PPropInfo;
begin
  OutList.BeginUpdate;
  try
    OutList.Clear;

    Count := GetPropList(Component.ClassInfo, tkAny, nil);
    Size  := Count * SizeOf(Pointer);
    GetMem(PropList, Size);
    try
      Count := GetPropList(Component.ClassInfo, tkAny, PropList);
      for I := 0 to Count -1 do
      begin
        PropInfo := PropList^[I];
        if not (PropInfo^.PropType^.Kind = tkMethod) then
        begin
          OutList.Add(PropInfo^.Name);
        end;
      end;
    finally
      FreeMem(PropList);
    end;
  finally
    OutList.EndUpdate;
  end;
end;

下面会输出属性值,如:

clWindow 错误 是的 25 75
function GetComponentPropertyValue(Component: TComponent; APropName: string): string;
var
  I: Integer;
  Count, Size: Integer;
  PropList: PPropList;
  PropInfo: PPropInfo;
begin
  Count := GetPropList(Component.ClassInfo, tkAny, nil);
  Size  := Count * SizeOf(Pointer);
  GetMem(PropList, Size);
  try
    Count := GetPropList(Component.ClassInfo, tkAny, PropList);
    for I := 0 to Count -1 do
    begin
      PropInfo := PropList^[I];
      if not (PropInfo^.PropType^.Kind = tkMethod) then
      begin
        if SameText(PropInfo^.Name, APropName) then
        begin
          Result := System.Variants.VarToStr(GetPropValue(Component, PropInfo^.Name));
          Exit;
        end;
      end;
    end;
  finally
    FreeMem(PropList);
  end;
end;

procedure GetComponentPropertyValues(Component: TComponent; OutList: TStrings);
var
  SL: TStringList;
  I: Integer;
begin
  SL := TStringList.Create;
  try
    GetComponentPropertyNames(Component, SL);
    for I := 0 to SL.Count -1 do
    begin
      OutList.Add(GetComponentPropertyValue(Component, SL.Strings[I]));
    end;
  finally
    SL.Free;
  end;
end;

最后,下面会输出字符串格式的属性类型,如:

TColor 布尔值 布尔值 整数 整数
function GetComponentPropertyType(Component: TComponent; APropName: string): string;
var
  SL: TStringList;
  I: Integer;
  PropInfo: TPropInfo;
  PropTypeName: string;
begin
  SL := TStringList.Create;
  try
    GetComponentPropertyNames(Component, SL);
    for I := 0 to SL.Count -1 do
    begin
      PropInfo := GetPropInfo(Component, SL.Strings[I])^;
      if SameText(PropInfo.Name, APropName) then
      begin
        PropTypeName := PropInfo.PropType^.Name;
        Result := PropTypeName;
        Exit;
      end;
    end;
  finally
    SL.Free;
  end;
end;

procedure GetComponentPropertyTypes(Component: TComponent; OutList: TStrings);
var
  SL: TStringList;
  I: Integer;
begin
  SL := TStringList.Create;
  try
    GetComponentPropertyNames(Component, SL);
    for I := 0 to SL.Count -1 do
    begin
      OutList.Add(GetComponentPropertyType(Component, SL.Strings[I]));
    end;
  finally
    SL.Free;
  end;
end;

上面调用的每个过程的输出并排显示如下:

颜色 | cl窗口 | TColor 双缓冲 |假 |布尔值 已启用 |真 |布尔值 高度 | 25 |整数 宽度 | 75 |整数

问题

此时以上所有内容都有效,除了我需要花一些时间阅读文档以尝试更好地理解并能够消化所有内容之外,没有其他问题。

我的问题(这几天一直困扰着我)是如何正确获取和设置子属性。例如,看看这个 Delphi Object Inspector 的屏幕截图(为目的而修改):

就像前面显示的过程一样,我需要对我以蓝色突出显示的那些子属性进行相同的操作。

理想情况下,我想要一个函数,我可以在其中传递组件和属性名称,如果它具有子属性,则返回 True,例如:

function HasSubProperty(Component: TComponent; APropName: string): Boolean;
begin
  Result := ??
end;

我不确定这将如何运作,但从屏幕截图中可以明显看出,一些子属性也有子属性(例如 Component>Font>Style)。

最终我想要的是一种检索子属性的名称、值和类型的方法。所以像:

procedure GetComponentSubPropertyNames(Component: TComponent; APropName: string;
  OutList: TStrings);
begin
  //
end;

调用时:

GetComponentSubPropertyNames(Label1, Anchors);

应该检索:

akLeft akTop akRight akBottom

使用类似的程序来获取值和类型将如下所示:

ak左 |真 |布尔值 akTop |真 |布尔值 akRight |真 |布尔值 akBottom |真 |布尔值

对于字体子属性,例如:

字符集 | DEFAULT_CHARSET | TFontCharset 颜色 | clWindowText | TColor 高度 | -11 |整数 方向 | 0 |整数 间距 | fp默认 | TFontPitch 质量 | fq默认 | TFontQuality 尺寸 | 8 |整数

访问另一个子属性(Font.Style)会带来另一个问题,除非使用这样的过程:

procedure GetComponentSubPropertySubPropertyNames(Component: TComponent; APropName, ASubPropName: string; OutList: TStrings);
begin
  //
end;

然后这变得相当愚蠢。


总结

基本上,我需要一种挖掘更深层次属性的方法,以获取它们的名称、值和类型,将它们放入列表中,并且还能够更改值。

如果有人可以编写一些代码示例来说明我如何实现这一点,我将不胜感激(代码中包含一些 cmets)。我确信对某些人来说这将是一项相对容易的任务,但我发现它确实非常微不足道。

说实话,到目前为止阅读了各种文档和示例仍然让我一无所知,主要担心的是不知道要使用什么类型或如何正确创建和管理它们。

【问题讨论】:

【参考方案1】:

TFont,TAction,TPopupMenu 等子属性已经是在 TButton 等所有者组件中创建的组件(类)。

要了解属性类型,请使用PropInfo.PropType^.Kind

查看 Delphi 帮助

TypInfo.PTypeInfo Type

TypInfo.TTypeKind

这是您请求的代码示例:

function HasSubProperty(Component: TComponent; APropName: string): Boolean;
var PropInfo: TPropInfo;
begin
  PropInfo := GetPropInfo(Component, APropName)^;
  Result := PropInfo.PropType^.Kind in [tkClass,tkSet,tkRecord]
end;

获取子类的示例

function GetSubPropClass(Component: TComponent; APropName: string):TComponent;
    var PropInfo: PPropInfo;
        AObject : TObject;
    begin
      Result := nil;
      PropInfo := GetPropInfo(Component, APropName);
      if PropInfo.PropType^.Kind in [tkClass] then
      begin
        AObject := GetObjectProp(Component,PropInfo);
        if Assigned(AObject) then
          Result := TComponent(AObject);
      end;
    end;

使用示例

procedure TForm1.Button1Click(Sender: TObject);
var AComp : TComponent;
begin
  AComp := GetSubPropClass(Form1,'TFont',ListBox4.Items);
  if AComp <> nil then
    GetComponentPropertyNames(AComp);
end;

更新

此代码将帮助您了解 SET 属性

function GetComponentPropertyValue(Component: TComponent; APropName: string): string;
var
  I,X: Integer;
  Count, Size: Integer;
  PropList: PPropList;
  PropInfo: PPropInfo;
  PropTypeInf : PTypeInfo;
  SetList : TStrings;
  SetName,SetVal : string;
begin
  Count := GetPropList(Component.ClassInfo, tkAny, nil);
  Size  := Count * SizeOf(Pointer);
  GetMem(PropList, Size);
  try
    Count := GetPropList(Component.ClassInfo, tkAny, PropList);
    for I := 0 to Count -1 do
    begin
     PropTypeInf := PropList^[I]^.PropType^;
     PropInfo := PropList^[I];
      if not (PropInfo^.PropType^.Kind = tkMethod) then
      begin
        if SameText(PropInfo^.Name, APropName) then
        begin

          if (PropInfo^.PropType^.Kind = tkSet) then
          begin
            try
              SetList := TStringList.Create;
              SetList.CommaText := System.Variants.VarToStr(GetPropValue(Component, PropInfo^.Name));
              for X := 0 to 255 do
              begin
                SetName := GetSetElementName(GetTypeData(PropTypeInf)^.CompType^,X);
                if ContainsStr(SetName,'UITypes') then break;
                SetVal := SetName + ' = ' + IfThen(SetList.IndexOf(SetName)<>-1,'True','False');
                if Result = '' then
                  Result := SetVal else
                  Result := Result + ', ' + SetVal;
              end;

            finally
              SetList.Free;
            end;
          end else
            Result := System.Variants.VarToStr(GetPropValue(Component, PropInfo^.Name));
          Exit;
        end;
      end;
    end;
  finally
    FreeMem(PropList);
  end;
end;

【讨论】:

HasSubProperty 似乎工作正常,实际上我认为我应该更好地将其命名为 HasSubProperties 但这是一个快速的更改。 PropInfo := GetPropInfo(Component, APropName); 似乎总是返回 nil 并在使用 GetSubPropClass 函数时引发 AV 错误。您的 Button1 事件中还有一个小错字,而不是 GetComponentPropertyNames(GetSubPropClass(Form1,'TFont',ListBox4.Items); 我相信应该是 GetComponentPropertyNames(GetSubPropClass(Form1,'TFont'), ListBox4.Items); :) 这是一个简单的例子,你必须在你的过程中使用它 GetComponentPropertyNames 并调用它 (Recursion algorithm) 或类似的东西 感谢您的回答和 cmets,但我在这里没有取得多大成功。代码类型有效,但只有当我通过Font 而不是TFont 时,它也适用于例如通过Margins,但是在通过Anchors 甚至StyleElements 时它会失败 Anchors 、 StyleElements 和 Align 是设置属性而不是对象,因此您无法获取子属性 OI 将设置属性显示为具有布尔属性的对象。

以上是关于使用 RTTI 时,我们如何获取和设置更深层次的子属性?的主要内容,如果未能解决你的问题,请参考以下文章

读《java编程思想》14-RTTI

类型信息(反射,RTTI)

java 类型信息

RTTI与反射之Java

没有 RTTI 的 C++ 双分派“可扩展”

Java基础RTTI与反射之Java