Tommi Prami 130 Posted April 11 (edited) Yellow, I have about following situation. FInstance is any TObject descendant and enum property can be any public or published Enum property, code must not link to hard coded enum type. procedure TMyThingy.SetEnumPropertyValue(const AValue: string); var LContext: TRttiContext; LRtttiType: TRttiType; LProperty: TRttiProperty; begin LContext := TRttiContext.Create; LRtttiType := LContext.GetType(FInstance.ClassType); LProperty := LRtttiType.GetProperty(FPropertyName); // Here I should convert lets say TMyEnum = (A, B, C) from string into the property value // if I call SetEnumPropertyValue('B') property FPropertyName from FInstance-object should be set. end; This should be quite simple, couple lines of code most likely. Could not find sample code that was good enough fit to get this to work, there usually was too much knowns, like enum type TMyEnum, This should be totally dynamic. Circled around this quite long time, just could not find the way to connect all the dots... -Tee- Edited April 11 by Tommi Prami Share this post Link to post
Dalija Prasnikar 1396 Posted April 11 I would use TypeInfo instead of RTTI as it will be faster. This requires System.TypInfo procedure TMyThingy.SetEnumPropertyValue(const AValue: string); begin PByte(@FEnumProp)^ := GetEnumValue(TypeInfo(TMyEnum), AValue); end; You should pay attention on enum type size and use appropriate sized pointer when casting PByte(@EnumProp)^. This will also raise out of range exception if passed string does not match to any value in TMyEnum. You can catch that and set it to some default value if needed. Share this post Link to post
Tommi Prami 130 Posted April 11 10 minutes ago, Dalija Prasnikar said: I would use TypeInfo instead of RTTI as it will be faster. This requires System.TypInfo procedure TMyThingy.SetEnumPropertyValue(const AValue: string); begin PByte(@FEnumProp)^ := GetEnumValue(TypeInfo(TMyEnum), AValue); end; You should pay attention on enum type size and use appropriate sized pointer when casting PByte(@EnumProp)^. This will also raise out of range exception if passed string does not match to any value in TMyEnum. You can catch that and set it to some default value if needed. I think I explained myself poorly. That won't work because it depends on TypeInfo(TMyEnum) My code must not know/depend on the (hardcoded) type, it should be dynamic. Work with any given property, that is any type of Enum (some limitations may apply, but if works simple enums like one in example, it is OK). Share this post Link to post
Lars Fosdal 1792 Posted April 11 What if you treat every value as a generic type? That way you are free to use TypeInfo? procedure TParam<T>.SetAsString(const aValue: string); var TV: TValue; begin TV := TValue.From<T>(Default(T)); try case TV.Kind of tkEnumeration: TV := TValue.FromOrdinal(TypeInfo(T), GetEnumValue(TypeInfo(T), aValue)); tkInteger: TV := TValue.From<Integer>(StrToInt(aValue)); tkInt64: TV := TValue.From<Int64>(StrToInt(aValue)); tkFloat: TV := TValue.From<Extended>(StrToFloat(aValue)); else TV := TValue.From<String>(aValue); end; FValue := TV.AsType<T>; except on E:Exception do begin CmdDebugOut(Parent.Debug + ': "' + aValue + '" -> ' + E.Message); FValue := Default(T); end; end; end; function TParam<T>.GetAsString: string; var TV: TValue; begin TV := TValue.From<T>(FValue); Result := TV.AsString; end; Share this post Link to post
msohn 28 Posted April 11 13 minutes ago, Tommi Prami said: My code must not know/depend on the (hardcoded) type, it should be dynamic. Work with any given property, that is any type of Enum (some limitations may apply, but if works simple enums like one in example, it is OK). I've been using TypeInfo.SetEnumProp for that - works for all Enums that are published properties. It basically is SetOrdProp with GetEnumValue shown by Dalija, so saves you figuring out the enum size. Share this post Link to post
Tommi Prami 130 Posted April 11 (edited) 7 minutes ago, msohn said: I've been using TypeInfo.SetEnumProp for that - works for all Enums that are published properties. It basically is SetOrdProp with GetEnumValue shown by Dalija, so saves you figuring out the enum size. Oh, I forgot to mention that should work public properties also, I modify original, I still can... MAybe it is now more clear what I am after... -Tee- Edited April 11 by Tommi Prami Share this post Link to post
Lars Fosdal 1792 Posted April 11 There also is https://docwiki.embarcadero.com/Libraries/Sydney/en/System.TypInfo.GetEnumProp which returns a PPropInfo, which again contains a PropType: PPTypeInfo, which you then can use with GetEnumValue? Share this post Link to post
Tommi Prami 130 Posted April 11 30 minutes ago, Lars Fosdal said: There also is https://docwiki.embarcadero.com/Libraries/Sydney/en/System.TypInfo.GetEnumProp which returns a PPropInfo, which again contains a PropType: PPTypeInfo, which you then can use with GetEnumValue? This lead me one step closer. var LEnumValue: Integer; LEnumValue := GetEnumValue(LProperty.PropertyType.Handle, AValue); if (LEnumValue <> -1) and LProperty.IsWritable then LProperty.SetValue(FInstance, LEnumValue); With this I get the actual value, but setting the value does not work (More than less same code is used elsewhere and they work). It raises EInvalidCast with message 'Invalid class typecast'. Integer is correct value for the Enum, it is in between bounds. This slightly baffles me now. Share this post Link to post
Stefan Glienke 2002 Posted April 12 (edited) TValue has no implicit casting rule for integer -> enum - thus it raises the invalid cast exception because you are passing an Integer to SetValue which gets implicit put into a TValue which gets transported further. It later does a type/cast check against the actual type of the property and fails. See TValue.Cast or TValue.TryCast and the Conversions matrix for more details. I mentioned this many times: TValue is not Variant - it only supports implicit type casts that also the language supports and Integer to Enum type and vice versa is not directly assignable. procedure SetEnumProp(Instance: TObject; const PropName: string; const Value: string); var c: TRttiContext; t: TRttiType; p: TRttiProperty; typInfo: PTypeInfo; enumValue: Integer; v: TValue; begin t := c.GetType(Instance.ClassInfo); p := t.GetProperty(PropName); typInfo := p.PropertyType.Handle; enumValue := GetEnumValue(typInfo, Value); v := TValue.FromOrdinal(typInfo, enumValue); p.SetValue(Instance, v); end; Edited April 12 by Stefan Glienke 2 Share this post Link to post
Tommi Prami 130 Posted April 15 On 4/12/2024 at 5:53 PM, Stefan Glienke said: TValue has no implicit casting rule for integer -> enum - thus it raises the invalid cast exception because you are passing an Integer to SetValue which gets implicit put into a TValue which gets transported further. It later does a type/cast check against the actual type of the property and fails. See TValue.Cast or TValue.TryCast and the Conversions matrix for more details. Have to try to remember that. Not used it too much. Thanks for helping! -Tee- Share this post Link to post