Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I create a generic TValue for enumerated RTTI field?

In the question here a method for creating a compatible TValue to use with SetValue is shown. I'm trying to make a generic version of this, to use RTTI to store a class into an INI file. This is my cut down code:

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;

In the answer referenced, the solution was to get the value using the TValue.From() method, but that appears to require a variable of the appropriate type. I don't have such a type as my code doesn't know what it is.

I am seeking an example of a generic way to obtain a value in a string from the RTTI, and put it back again afterward. I've not found a good tutorial that covers this yet.

like image 438
mj2008 Avatar asked Aug 24 '11 15:08

mj2008


2 Answers

You must get an instance to the TValue to set before assing a value, and then convert the string to the enumerated value using the GetEnumValue function

Try this code :

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;
like image 158
RRUZ Avatar answered Oct 06 '22 21:10

RRUZ


Here is some example code showing how do this:

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;
like image 21
Robert Love Avatar answered Oct 06 '22 21:10

Robert Love