【问题标题】:How to loop all properties in a Class如何循环一个类中的所有属性
【发布时间】:2012-04-28 15:05:49
【问题描述】:

我的 Delphi 应用程序中有一个类,我想要一种简单而动态的方法将所有字符串属性重置为 '' 并将所有布尔属性重置为 False 据我在网上看到的,应该可以制作某种循环,但我不清楚如何做。

【问题讨论】:

  • 你用的是哪个delphi版本?
  • 好吧,我忘记了——我使用的是 Delphi XE

标签: delphi class properties delphi-xe rtti


【解决方案1】:

如果您是 Delphi 2010(及更高版本)用户,则有一个新的 RTTI 单元 (rtti.pas)。您可以使用它来获取有关您的类及其属性的运行时信息(默认情况下为公共属性,但您可以使用{$RTTI} 编译器指令来包含受保护和私有字段信息)。 例如,我们有下一个包含 3 个公共字段(1 个布尔字段和 2 个字符串字段(其中一个是只读字段))的测试类。

    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;

要枚举对象的所有属性并将其值设置为默认,您可以使用类似下面的代码。 首先你必须初始化TRttiContext 结构(这不是必需的,因为它是一个记录)。然后你应该得到关于你的对象的 rtti 信息,之后你可以循环你的属性并过滤它(跳过只读属性和布尔和搅拌除外)。考虑到字符串的种类很少:tkUString、tkString 和其他(看看typinfo.pas 中的TTypeKind

    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;

简单的测试代码:

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.

结果是

string1 : test1
string2 : test2
bool: True
reset values

string1 :
string2 : test2
bool: False

也看看属性,我认为用一些属性标记属性(你需要重置)是个好主意,并且可能使用默认值,例如:

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

那么您可以只过滤带有ResetToAttribute 标记的属性

【讨论】:

  • 当然,我现在有很多信息要处理,我认为 teran 的解决方案将是我将尝试的解决方案。但我会回来的。
  • 我重写了答案,因为我误读了您的问题(认为您需要它用于组件)。但是,对于您自己的具有非已发布属性的类,旧样式的 RTTI 无法使用。所以一定要按照这种方式;-)
【解决方案2】:

请注意,以下代码仅适用于类的已发布属性!此外,传递给下面函数的类的实例必须至少定义已发布的部分!

这里是如何使用旧样式 RTTI 将已发布的字符串属性值设置为空字符串并将布尔值设置为 False。

如果您的 Delphi 早于 Delphi 2009,您可能会缺少 tkUString 类型。如果是这样,只需从以下代码中删除
它:

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;

这是一个简单的测试代码(注意必须发布属性;如果类中没有发布的属性,至少必须有空的发布部分):

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;

【讨论】:

  • 抱歉耽搁了回来-忙于其他项目:-)
  • @David,谢谢!这就是我在initial revision 中一直在做的事情。我不知道我为什么要那样打破它......
猜你喜欢
  • 1970-01-01
  • 2011-03-21
  • 1970-01-01
  • 2010-10-06
  • 1970-01-01
  • 2014-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多