在问题here中,显示了用于创建与SetValue一起使用的兼容TValue的方法。我正在尝试制作一个通用版本,使用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获取字符串中的值,然后再将其重新放回。我还没有找到一个很好的教程来解决这个问题。
答案 0 :(得分:7)
在分配值之前,您必须获取要设置的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;
答案 1 :(得分:6)
以下是一些示例代码,说明了如何执行此操作:
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;
答案 2 :(得分:0)
我有同样的问题,但我用另一种方式解决了。更快捷的方式:
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;