如何为枚举的RTTI字段创建通用TValue?

时间:2011-08-24 15:09:28

标签: delphi delphi-xe rtti

在问题here中,显示了用于创建与SetValue一起使用的兼容TValue的方法。我正在尝试制作一个通用版本,使用RTTI将类存储到INI文件中。这是我的缩减代码:

procedure TMyClass.LoadRTTI(xObject: TObject);
var
  LContext: TRttiContext;
  LClass: TRttiInstanceType;
  xField : TRttiField;
  szNewValue : String;
  xValue : TValue;
begin
  LContext := TRttiContext.Create;
  LClass := LContext.GetType(xObject.ClassType) as TRttiInstanceType;

  for xField in LClass.GetDeclaredFields do
  begin
    szNewValue := IniFile.ReadString(szSection, xField.Name, '');
    if szNewValue <> '' then // emumerated will be '0' (zero) as that is what GetValue.AsString returns
    begin
      case xField.FieldType.TypeKind of
      tkEnumeration: xValue := StrToIntDef(szNewValue, xField.GetValue(xObject).AsOrdinal);
      end;
      xField.SetValue(xObject, xValue); // FAILS HERE with 'Invalid calss typecast
    end;
  end;
end;

在引用的答案中,解决方案是使用TValue.From()方法获取值,但似乎需要适当类型的变量。我没有这样的类型,因为我的代码不知道它是什么。

我正在寻找一个通用方法的例子来从RTTI获取字符串中的值,然后再将其重新放回。我还没有找到一个很好的教程来解决这个问题。

3 个答案:

答案 0 :(得分:7)

在分配值之前,您必须获取要设置的TValue的实例,然后使用GetEnumValue函数将字符串转换为枚举值

试试这段代码:

procedure TMyClass.LoadRTTI(xObject: TObject);
var
  LContext: TRttiContext;
  LClass: TRttiInstanceType;
  xField : TRttiField;
  szNewValue : String;
  xValue : TValue;
begin
  LContext := TRttiContext.Create;
  LClass := LContext.GetType(xObject.ClassType) as TRttiInstanceType;

  for xField in LClass.GetDeclaredFields do
  begin
    szNewValue := IniFile.ReadString(szSection, xField.Name, '');
    if szNewValue <> '' then // emumerated will be '0' (zero) as that is what GetValue.AsString returns
    begin
      case xField.FieldType.TypeKind of
      tkEnumeration: 
                   begin
                     //get the instance to the TValue to set
                     xValue:=xField.GetValue(xObject);
                     //convert the data to a valid TValue
                     xValue:=TValue.FromOrdinal(xValue.TypeInfo,GetEnumValue(xValue.TypeInfo,szNewValue));
                   end;

      end;
      //assign the new value from the TValue
      xField.SetValue(xObject, xValue); 
    end;
  end;
end;

答案 1 :(得分:6)

以下是一些示例代码,说明了如何执行此操作:

var
  V : TValue;
  OrdValue : Integer;
  C : TRttiContext;
  F : TRttiField;
  lTypeInfo : PTypeInfo;
begin

  // Pick a Enumerated Field
  F := C.GetType(TForm).GetField('FFormStyle');

  // Get the TypeInfo for that field
  lTypeInfo := F.FieldType.Handle;

  // Setting TValue from an Enumeration Directly.
  V := TValue.From(FormStyle);
  ShowMessage(V.ToString);
  // Setting TValue from the ordinal value of a Enumeration
  OrdValue := ord(FormStyle);
  V := TValue.FromOrdinal(lTypeInfo,OrdValue);
  ShowMessage(V.ToString);
  // Setting TValue from the String Value of an enumeration.
  OrdValue := GetEnumValue(lTypeInfo,'fsStayOnTop');
  V := TValue.FromOrdinal(lTypeInfo,OrdValue);
  ShowMessage(V.ToString);
end;

答案 2 :(得分:0)

我有同样的问题,但我用另一种方式解决了。更快捷的方式:

type
  CustType = (ctNone, ctEverything, ctNothing);

  TObjctCust = class(TObject)
    InfoType: CustType;
  end;

procedure TForm34.Button1Click(Sender: TObject);
var
  CurContext: TRttiContext;
  Test: TObjctCust;
  CurClassType: TRttiType;
  CurFields: TArray<TRttiField>;
  I: Integer;
  Field: TRttiField;
  TypeValue: Integer;
  LFieldPointer: Pointer;
  TypedSmallInt: SmallInt;
begin
  Test := TObjctCust.Create;

  CurContext := TRttiContext.Create;
  CurClassType := CurContext.GetType(Test.ClassType);
  CurFields := CurClassType.GetFields;

  //Here you can set any integer value you'd like to set in the type field. For example the result of query (AsInteger, AsOrdinal)
  TypeValue := 1;
  for I := 0 to Length(CurFields) -1 do
  begin
    Field := CurFields[I];
    if Field.FieldType.TypeKind = tkEnumeration then
    begin
      //Here is the solution, I change the value direct in the field position
      LFieldPointer := Pointer(PByte(Test) + Field.Offset);
      TypedSmallInt := TypeValue;
      Move(TypedSmallInt, LFieldPointer^, Field.FieldType.TypeSize);
    end;
  end;

  ShowMessage(IntToStr(Ord(Test.InfoType)));
end;