Jump to content
Tommi Prami

Set enum property with TRttiProperty from string

Recommended Posts

Posted (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 by Tommi Prami

Share this post


Link to post

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

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
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
Posted (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 by Tommi Prami

Share this post


Link to post
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
Posted (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 by Stefan Glienke
  • Thanks 2

Share this post


Link to post
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

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×