【问题标题】:How do I create a generic TValue for enumerated RTTI field?如何为枚举的 RTTI 字段创建通用 TValue?
【发布时间】:2011-08-24 15:09:27
【问题描述】:

在问题here 中显示了一种创建兼容TValue 以与SetValue 一起使用的方法。我正在尝试制作一个通用版本,以使用 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 获取字符串中的值的通用方法示例,然后再将其放回原处。我还没有找到一个很好的教程来涵盖这个。

【问题讨论】:

    标签: delphi delphi-xe rtti


    【解决方案1】:

    在赋值之前必须先获取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;
    

    【讨论】:

    • 好的,一旦我不再聪明地保存代码,它就会运作良好。要获取要保存到 INI 的值,只需使用 xField.GetValue(xObject).ToString;
    【解决方案2】:

    下面是一些示例代码,展示了如何做到这一点:

    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;
    

    【讨论】:

    • 谢谢你 - 这是一组很好的选项和替代方案,可以让问题更好地记录如何在其他情况下执行此操作。
    【解决方案3】:

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

    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;
    

    【讨论】:

      猜你喜欢
      • 2011-06-01
      • 2018-04-18
      • 2011-07-22
      • 1970-01-01
      • 2021-12-13
      • 2014-03-11
      • 2013-11-21
      • 1970-01-01
      • 2011-02-26
      相关资源
      最近更新 更多