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, xFiel开发者_运维知识库d.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.
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;
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;
I had the same problem, but I solved it another way. A faster way:
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;
精彩评论