2011-08-24 13 views
5

En la pregunta here, se muestra un método para crear un TValue compatible para usar con SetValue. Estoy tratando de hacer una versión genérica de esto, para usar RTTI para almacenar una clase en un archivo INI. Este es mi reducir código:¿Cómo creo un TValue genérico para el campo RTTI enumerado?

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; 

En la respuesta se hace referencia, la solución era conseguir el valor utilizando el método TValue.From(), pero que parece requerir una variable del tipo apropiado. No tengo ese tipo porque mi código no sabe de qué se trata.

Estoy buscando un ejemplo de una forma genérica para obtener un valor en una cadena del RTTI, y volver a colocarlo después. No he encontrado un buen tutorial que cubra esto todavía.

Respuesta

7

Debe obtener una instancia a la TValue poner delante de assing un valor y, a continuación, convertir la cadena en el valor enumerado utilizando la función GetEnumValue

probar este código:

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; 
+0

De acuerdo, funcionó bien una vez que dejé de ser inteligente en mi código de guardado. Para obtener el valor para guardar en el INI, simplemente use xField.GetValue (xObject) .ToString; – mj2008

6

Aquí está algo código de ejemplo que muestra cómo hacer esto:

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

Gracias por eso, es un buen conjunto de opciones y alternativas que harán que la pregunta sea un mejor registro de cómo hacerlo en otras situaciones. – mj2008

0

Tuve el mismo problema, pero lo resolví de otra manera. Una forma más rápida:

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; 
Cuestiones relacionadas