When using RTTI, how can we get and set deeper sub-properties?

Overview

I appreciate a couple of similar questions that have already been asked before:

However, I will not further understand how exactly RTTI can be used for my needs.

I also put a lot of time and effort into writing this question, so I hope it doesn't close :)

Working examples

I have several procedures below that can list property names, values, and component types in a TStrings list. The original source was not mine, I just made some minor changes to it, tidied up the code, and put them into some neat reusable procedures:


Property names are listed below, for example:

  • Color
  • Doublebuffered
  • Included
  • 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; 

The following are property values, such as:

  • 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; 

And finally, property types in string format will be displayed below, for example:

  • 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; 

Next to each other, the output for each procedure described above will look something like this:

  • Color | clWindow | TColor
  • Doublebuffered | False | Logical
  • Included | True | Logical
  • Height | 25 | Integer
  • Width | 75 | Integer

Question

All of the above works at this stage, there is no other problem, except that I need to read the documentation a bit to try to get a little better understanding and be able to digest it all.

My question (which has been listening to me for several days) is how to properly obtain and set additional properties. For example, take a look at this screenshot (modified for the purpose) of the Delphi Object Inspector:

enter image description here

Just like the previous procedures, I need the same thing as for those auxiliary properties that I highlighted in blue.

Ideally, I would like to use a function in which I can pass the component and property name, and return True if it has sub properties, so something like:

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

I'm not sure how well this will work, although it can be seen from the screenshot, some additional properties also have auxiliary properties (such as Component> Font> Style).

Ultimately, what I would like is a way to get the names, values, and types of additional properties. So something like:

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

When called:

 GetComponentSubPropertyNames(Label1, Anchors); 

Must get:

  • akLeft
  • akTop
  • akRight
  • akBottom

With similar procedures to get values โ€‹โ€‹and types it will look:

  • akLeft | True | Logical
  • akTop | True | Logical
  • akRight | True | Logical
  • akBottom | True | Logical

For additional font properties, for example:

  • Charset | DEFAULT_CHARSET | TFontCharset
  • Color | clWindowText | TColor
  • Height | -11 | Integer
  • Orientation | 0 | Integer
  • Pitch | fpDefault | TFontPitch
  • Quality | fqDefault | TFontQuality
  • Size | 8 | Integer

Accessing another helper property (Font.Style) then creates another problem if the procedure is not used:

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

It gets pretty dumb.


Summary

Basically, I need a way to dig into deeper level properties in order to get the names, values โ€‹โ€‹and types of them, put them in a list, and also be able to change the values.

I would appreciate it if someone could write code samples, how can I achieve this (a bonus with some comments in the code). I am sure that for some this will be a relatively simple task, but I find it very trivial.

After reading various documents and examples that still leave me rather ignorant to be honest, the main problem is not to know which types to use or how to properly create and manage them.

+6
source share
1 answer

Sub-properties, such as TFont, TAction, TPopupMenu , are already components (classes) created in the owner component, for example TButton .

To find out the type of property, use PropInfo.PropType^.Kind

See Delphi Help

Type TypInfo.PTypeInfo

TypInfo.TTypeKind

Here is an example of the code you are requesting:

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

Subclass Example

 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; 

Usage example

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

UPDATE

This code will help you understand the properties of 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; 
+2
source

All Articles