使用 RTTI 时,我们如何获取和设置更深层次的子属性?
When using RTTI how can we get and set deeper level sub-properties?
概览
感谢之前已经有人问过几个类似的问题:
- 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 列表。原始源不是我的,我只是对其进行了一些小改动,整理了代码并将它们放入了一些整洁的可重用程序中:
下面会输出属性个名字,如:
- Color
- DoubleBuffered
- Enabled
- Height
- Width
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
- False
- True
- 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
- Boolean
- Boolean
- Integer
- Integer
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;
上面调用的每个过程的输出并排显示如下:
- Color | clWindow | TColor
- DoubleBuffered | False | Boolean
- Enabled | True | Boolean
- Height | 25 | Integer
- Width | 75 | Integer
问题
此时以上所有方法都有效,除了我需要花一些时间多阅读文档以尝试更好地理解并能够消化所有内容外,没有其他问题。
我的问题(这几天一直困扰着我)是如何正确获取和设置子属性。例如,看一下 Delphi 对象检查器的屏幕截图(有目的的修改):
就像之前显示的过程一样,我需要对我以蓝色突出显示的那些子属性进行同样的操作。
理想情况下,我想要的是一个函数,我可以在其中传递一个组件和一个 属性 名称,如果它具有子属性,则 return 为真,所以类似于:
function HasSubProperty(Component: TComponent; APropName: string): Boolean;
begin
Result := ??
end;
虽然从屏幕截图中可以明显看出,我不确定这将如何工作,但一些子属性也有子属性(例如组件>字体>样式)。
最终我想要的是一种检索子属性的名称、值和类型的方法。所以像:
procedure GetComponentSubPropertyNames(Component: TComponent; APropName: string;
OutList: TStrings);
begin
//
end;
调用时:
GetComponentSubPropertyNames(Label1, Anchors);
应该检索:
- akLeft
- akTop
- akRight
- akBottom
使用类似的过程来获取值和类型如下所示:
- akLeft | True | Boolean
- akTop | True | Boolean
- akRight | True | Boolean
- akBottom | True | Boolean
对于字体子属性,例如:
- Charset | DEFAULT_CHARSET | TFontCharset
- Color | clWindowText | TColor
- Height | -11 | Integer
- Orientation | 0 | Integer
- Pitch | fpDefault | TFontPitch
- Quality | fqDefault | TFontQuality
- Size | 8 | Integer
访问另一个子 属性 (Font.Style) 会带来另一个问题,除非使用这样的过程:
procedure GetComponentSubPropertySubPropertyNames(Component: TComponent; APropName, ASubPropName: string; OutList: TStrings);
begin
//
end;
这就变得相当愚蠢了。
总结
基本上,我需要一种深入挖掘更深层次属性的方法,以获取它们的名称、值和类型,将它们放入列表中,并且还能够更改值。
如果有人可以编写一些代码示例来说明我如何实现这一点,我将不胜感激(代码中带有一些注释的奖励)。我相信对于某些人来说这将是一项相对容易的任务,但我发现它确实非常微不足道。
到目前为止,阅读了各种文档和示例后,老实说,我仍然一无所知,主要问题是不知道要使用什么类型或如何正确创建和管理它们。
TFont、TAction、TPopupMenu 等子属性已经是在 TButton 等所有者组件中创建的组件(类) .
要知道 属性 键入使用 PropInfo.PropType^.Kind
查看Delphi帮助
这是您请求的代码示例:
function HasSubProperty(Component: TComponent; APropName: string): Boolean;
var PropInfo: TPropInfo;
begin
PropInfo := GetPropInfo(Component, APropName)^;
Result := PropInfo.PropType^.Kind in [tkClass,tkSet,tkRecord]
end;
获取子项的示例class
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;
概览
感谢之前已经有人问过几个类似的问题:
- 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 列表。原始源不是我的,我只是对其进行了一些小改动,整理了代码并将它们放入了一些整洁的可重用程序中:
下面会输出属性个名字,如:
- Color
- DoubleBuffered
- Enabled
- Height
- Width
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
- False
- True
- 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
- Boolean
- Boolean
- Integer
- Integer
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;
上面调用的每个过程的输出并排显示如下:
- Color | clWindow | TColor
- DoubleBuffered | False | Boolean
- Enabled | True | Boolean
- Height | 25 | Integer
- Width | 75 | Integer
问题
此时以上所有方法都有效,除了我需要花一些时间多阅读文档以尝试更好地理解并能够消化所有内容外,没有其他问题。
我的问题(这几天一直困扰着我)是如何正确获取和设置子属性。例如,看一下 Delphi 对象检查器的屏幕截图(有目的的修改):
就像之前显示的过程一样,我需要对我以蓝色突出显示的那些子属性进行同样的操作。
理想情况下,我想要的是一个函数,我可以在其中传递一个组件和一个 属性 名称,如果它具有子属性,则 return 为真,所以类似于:
function HasSubProperty(Component: TComponent; APropName: string): Boolean;
begin
Result := ??
end;
虽然从屏幕截图中可以明显看出,我不确定这将如何工作,但一些子属性也有子属性(例如组件>字体>样式)。
最终我想要的是一种检索子属性的名称、值和类型的方法。所以像:
procedure GetComponentSubPropertyNames(Component: TComponent; APropName: string;
OutList: TStrings);
begin
//
end;
调用时:
GetComponentSubPropertyNames(Label1, Anchors);
应该检索:
- akLeft
- akTop
- akRight
- akBottom
使用类似的过程来获取值和类型如下所示:
- akLeft | True | Boolean
- akTop | True | Boolean
- akRight | True | Boolean
- akBottom | True | Boolean
对于字体子属性,例如:
- Charset | DEFAULT_CHARSET | TFontCharset
- Color | clWindowText | TColor
- Height | -11 | Integer
- Orientation | 0 | Integer
- Pitch | fpDefault | TFontPitch
- Quality | fqDefault | TFontQuality
- Size | 8 | Integer
访问另一个子 属性 (Font.Style) 会带来另一个问题,除非使用这样的过程:
procedure GetComponentSubPropertySubPropertyNames(Component: TComponent; APropName, ASubPropName: string; OutList: TStrings);
begin
//
end;
这就变得相当愚蠢了。
总结
基本上,我需要一种深入挖掘更深层次属性的方法,以获取它们的名称、值和类型,将它们放入列表中,并且还能够更改值。
如果有人可以编写一些代码示例来说明我如何实现这一点,我将不胜感激(代码中带有一些注释的奖励)。我相信对于某些人来说这将是一项相对容易的任务,但我发现它确实非常微不足道。
到目前为止,阅读了各种文档和示例后,老实说,我仍然一无所知,主要问题是不知道要使用什么类型或如何正确创建和管理它们。
TFont、TAction、TPopupMenu 等子属性已经是在 TButton 等所有者组件中创建的组件(类) .
要知道 属性 键入使用 PropInfo.PropType^.Kind
查看Delphi帮助
这是您请求的代码示例:
function HasSubProperty(Component: TComponent; APropName: string): Boolean;
var PropInfo: TPropInfo;
begin
PropInfo := GetPropInfo(Component, APropName)^;
Result := PropInfo.PropType^.Kind in [tkClass,tkSet,tkRecord]
end;
获取子项的示例class
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;