Как создать общее TValue для перечисляемого поля RTTI? - PullRequest
5 голосов
/ 24 августа 2011

В вопросе здесь показан способ создания совместимого значения 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 и затем вернуть его обратно.Я еще не нашел хорошего учебника, который бы охватывал это.

Ответы [ 3 ]

7 голосов
/ 24 августа 2011

Вы должны получить экземпляр для 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;
6 голосов
/ 24 августа 2011

Вот пример кода, показывающего, как это сделать:

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;
0 голосов
/ 20 мая 2015

У меня была такая же проблема, но я решил ее по-другому. Более быстрый способ:

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;
...