How to loop all properties in a Class
Asked Answered
T

2

14

I have a class in my Delphi app where I would like an easy and dynamic way of resetting all the string properties to '' and all the boolean properties to False As far as I can see on the web it should be possible to make a loop of some sort, but how to do it isn't clear to me.

Threemaster answered 17/4, 2012 at 9:18 Comment(2)
Which delphi version are you using?Sponsor
OK that I forgot - I am using Delphi XEThreemaster
H
13

Please note, the following code works only for published properties of a class! Also, the instance of a class passed to the function below must have at least published section defined!

Here is how to set the published string property values to an empty string and boolean values to False by using the old style RTTI.

If you have Delphi older than Delphi 2009 you might be missing the tkUString type. If so, simply remove
it from the following code:

uses
  TypInfo;

procedure ResetPropertyValues(const AObject: TObject);
var
  PropIndex: Integer;
  PropCount: Integer;
  PropList: PPropList;
  PropInfo: PPropInfo;
const
  TypeKinds: TTypeKinds = [tkEnumeration, tkString, tkLString, tkWString,
    tkUString];
begin
  PropCount := GetPropList(AObject.ClassInfo, TypeKinds, nil);
  GetMem(PropList, PropCount * SizeOf(PPropInfo));
  try
    GetPropList(AObject.ClassInfo, TypeKinds, PropList);
    for PropIndex := 0 to PropCount - 1 do
    begin
      PropInfo := PropList^[PropIndex];
      if Assigned(PropInfo^.SetProc) then
      case PropInfo^.PropType^.Kind of
        tkString, tkLString, tkUString, tkWString:
          SetStrProp(AObject, PropInfo, '');
        tkEnumeration:
          if GetTypeData(PropInfo^.PropType^)^.BaseType^ = TypeInfo(Boolean) then
            SetOrdProp(AObject, PropInfo, 0);
      end;
    end;
  finally
    FreeMem(PropList);
  end;
end;

Here is a simple test code (note the properties must be published; if there are no published properties in the class, at least empty published section must be there):

type
  TSampleClass = class(TObject)
  private
    FStringProp: string;
    FBooleanProp: Boolean;
  published
    property StringProp: string read FStringProp write FStringProp;
    property BooleanProp: Boolean read FBooleanProp write FBooleanProp;
  end;

procedure TForm1.Button1Click(Sender: TObject);
var
  SampleClass: TSampleClass;
begin
  SampleClass := TSampleClass.Create;
  try
    SampleClass.StringProp := 'This must be cleared';
    SampleClass.BooleanProp := True;
    ResetPropertyValues(SampleClass);
    ShowMessage('StringProp = ' + SampleClass.StringProp + sLineBreak +
      'BooleanProp = ' + BoolToStr(SampleClass.BooleanProp));
  finally
    SampleClass.Free;
  end;
end;
Hexachord answered 17/4, 2012 at 10:43 Comment(2)
Sorry for the delay in getting back - been busy with other projects :-)Threemaster
@David, thanks! That's what I've been doing in my initial revision. I don't know why did I break it that way...Hexachord
D
16

if you are an Delphi 2010 (and higher) user then there is a new RTTI unit (rtti.pas). you can use it to get runtime information about your class and its properties (public properties by default, but you can use {$RTTI} compiler directive to include protected and private fields information). For example we have next test class with 3 public fields (1 boolean and 2 string fields (one of them is readonly)).

    TTest = class(TObject)
      strict private
        FString1 : string;
        FString2 : string;
        FBool : boolean;
      public
        constructor Create();
        procedure PrintValues();

        property String1 : string read FString1 write FString1;
        property String2 : string read FString2;
        property BoolProp : boolean read FBool write FBool;
    end;

constructor TTest.Create();
begin
    FBool := true;
    FString1 := 'test1';
    FString2 := 'test2';
end;

procedure TTest.PrintValues();
begin
    writeln('string1 : ', FString1);
    writeln('string2 : ', FString2);
    writeln('bool: ', BoolToStr(FBool, true));
end;

to enumerate all properties of object and set it values to default you can use something like code below. First at all you have to init TRttiContext structure (it is not neccesary, because it is a record). Then you should get rtti information about your obejct, after that you can loop your properties and filter it (skip readonly properties and other than boolean and stirng). Take into account that there are few kind of strings : tkUString, tkString and others (take a look at TTypeKind in typinfo.pas)

    TObjectReset = record
      strict private
      public
        class procedure ResetObject(obj : TObject);  static;
    end;

{ TObjectReset }

class procedure TObjectReset.ResetObject(obj: TObject);
var ctx : TRttiContext;
    rt : TRttiType;
    prop : TRttiProperty;
    value : TValue;
begin
    ctx := TRttiContext.Create();
    try
        rt := ctx.GetType(obj.ClassType);

        for prop in rt.GetProperties() do begin
            if not prop.IsWritable then continue;

            case prop.PropertyType.TypeKind of
                tkEnumeration : value := false;
                tkUString :      value := '';
                else continue;
            end;
            prop.SetValue(obj, value);
        end;
    finally
        ctx.Free();
    end;
end;

simple code to test:

var t : TTest;
begin
    t := TTest.Create();
    try
        t.PrintValues();
        writeln('reset values'#13#10);
        TObjectReset.ResetObject(t);
        t.PrintValues();
    finally
        readln;
        t.Free();
    end;
end.

and result is

string1 : test1
string2 : test2
bool: True
reset values

string1 :
string2 : test2
bool: False

also take a look at Attributes, imo it is good idea to mark properties (wich you need to reset) with some attribute, and may be with default value like:

[ResetTo('my initial value')]
property MyValue : string read FValue write FValue;

then you can filter only properties wich are marked with ResetToAttribute

Dynamoelectric answered 17/4, 2012 at 10:42 Comment(2)
Sure I have a lot of info to work on now I think the solution by teran will be the one I will experiment with. But I will get back.Threemaster
I rewrote the answer because I misread your question (thinking you need it for components). However for your own classes with properties other than published is the old style RTTI unusable. So definitely follow this way ;-)Hexachord
H
13

Please note, the following code works only for published properties of a class! Also, the instance of a class passed to the function below must have at least published section defined!

Here is how to set the published string property values to an empty string and boolean values to False by using the old style RTTI.

If you have Delphi older than Delphi 2009 you might be missing the tkUString type. If so, simply remove
it from the following code:

uses
  TypInfo;

procedure ResetPropertyValues(const AObject: TObject);
var
  PropIndex: Integer;
  PropCount: Integer;
  PropList: PPropList;
  PropInfo: PPropInfo;
const
  TypeKinds: TTypeKinds = [tkEnumeration, tkString, tkLString, tkWString,
    tkUString];
begin
  PropCount := GetPropList(AObject.ClassInfo, TypeKinds, nil);
  GetMem(PropList, PropCount * SizeOf(PPropInfo));
  try
    GetPropList(AObject.ClassInfo, TypeKinds, PropList);
    for PropIndex := 0 to PropCount - 1 do
    begin
      PropInfo := PropList^[PropIndex];
      if Assigned(PropInfo^.SetProc) then
      case PropInfo^.PropType^.Kind of
        tkString, tkLString, tkUString, tkWString:
          SetStrProp(AObject, PropInfo, '');
        tkEnumeration:
          if GetTypeData(PropInfo^.PropType^)^.BaseType^ = TypeInfo(Boolean) then
            SetOrdProp(AObject, PropInfo, 0);
      end;
    end;
  finally
    FreeMem(PropList);
  end;
end;

Here is a simple test code (note the properties must be published; if there are no published properties in the class, at least empty published section must be there):

type
  TSampleClass = class(TObject)
  private
    FStringProp: string;
    FBooleanProp: Boolean;
  published
    property StringProp: string read FStringProp write FStringProp;
    property BooleanProp: Boolean read FBooleanProp write FBooleanProp;
  end;

procedure TForm1.Button1Click(Sender: TObject);
var
  SampleClass: TSampleClass;
begin
  SampleClass := TSampleClass.Create;
  try
    SampleClass.StringProp := 'This must be cleared';
    SampleClass.BooleanProp := True;
    ResetPropertyValues(SampleClass);
    ShowMessage('StringProp = ' + SampleClass.StringProp + sLineBreak +
      'BooleanProp = ' + BoolToStr(SampleClass.BooleanProp));
  finally
    SampleClass.Free;
  end;
end;
Hexachord answered 17/4, 2012 at 10:43 Comment(2)
Sorry for the delay in getting back - been busy with other projects :-)Threemaster
@David, thanks! That's what I've been doing in my initial revision. I don't know why did I break it that way...Hexachord

© 2022 - 2024 — McMap. All rights reserved.