I require to modify object set properties at runtime. I have been able to get as far as retrieving the Set values as below but I cannot figure out how to modify the set.
(Ptx is the TRttiProperty and has already been passed as a TRttiSetType)
var
LType: TRttiSetType;
Ctx: TRttiContext;
LOrd: TRttiOrdinalType;
LNames: Vector<string>;
begin
LType := Ctx.GetType(Ptx.TypeInfo) as TRttiSetType;
LOrd := LType.ElementType as TRttiOrdinalType;
LNames.Assign((LOrd as TRttiEnumerationType).GetNames);
ShowMessage(String.Join('|', LNames.Data));
end;
How can i modify or assign new values to the property Ptx?
The Rtti props classes for properties all have a SetValue method - which takes a TValue - creating a TValue from a set should be a call to TValue.From - ymmv depending on delphi version.
You can also just use the old System.TypInfo functions to do it - sometimes that is simpler.
My software relies heavily on rtti and I’m using GetValue/ SetValue everywhere except when a tkSet property is encountered. An example is TStyleElements [seFont, seClient, seBorder].
Currently, I can read ToString (a method for all TObject) and get the text representation ie ‘[seFont,seBorder]’. However, I want to be able to set TStyleElements (or any other set property) from a text representation. As far as I can tell, there isn’t even a straightforward way of saving a set property to Json other than the ToString value.
unit VSoft.Core.Utils.Sets;
{ DUnit Tested in ..\Tests\TestVSoftSetUtils.pas }
interface
uses
TypInfo,
Windows;
type
TSetUtils = class
public
class function SetToStringFB(const ATypeInfo: PTypeInfo; const Value): string;
class function StringToSetFB(const ATypeInfo: PTypeInfo; const Value: string): Integer;
end;
implementation
uses
SysUtils;
class function TSetUtils.SetToStringFB(const ATypeInfo: PTypeInfo; const Value): string;
var
I: Integer;
Data: PTypeData; // set's type data
EnumInfo: PTypeInfo; // set's base type info
EnumData: PTypeData; // set's base type data
begin
if ATypeInfo.Kind <> tkSet then
Result := ''
else
begin
Data := GetTypeData(ATypeInfo);
EnumInfo := Data^.CompType^;
EnumData := GetTypeData(EnumInfo);
Assert(EnumInfo.Kind in [tkEnumeration, tkInteger]);
Result := '[';
for I := EnumData.MinValue to EnumData.MaxValue do
if I in TIntegerSet(Value) then
begin
// The element is in the set, so add its name to the string.
if Length(Result) > 1 then
Result := Result + ','; // separate items with commas
Result := Result + GetEnumName(EnumInfo, I);
end;
Result := Result + ']';
end;
end;
class function TSetUtils.StringToSetFB(const ATypeInfo: PTypeInfo; const Value: string): Integer;
var
P: PChar;
EnumName: string;
EnumValue: Longint;
Data: PTypeData; // set's type data
EnumInfo: PTypeInfo; // set's base type info
// grab the next enum name
function NextWord(var P: PChar): string;
var
i: Integer;
begin
i := 0;
// scan til whitespace
while not (SysUtils.CharInSet(P[i],[',', ' ', #0, ']'])) do
Inc(i);
SetString(Result, P, i);
// skip whitespace
while SysUtils.CharInSet(P[i],[',', ' ', ']']) do
Inc(i);
Inc(P, i);
end;
begin
Result := 0;
if Value = '' then Exit;
P := PChar(Value);
Data := GetTypeData(ATypeInfo);
EnumInfo := Data^.CompType^;
// skip leading bracket and whitespace
while SysUtils.CharInSet(P^, ['[', ' ']) do
Inc(P);
EnumName := NextWord(P);
while EnumName <> '' do
begin
EnumValue := GetEnumValue(EnumInfo, EnumName);
if EnumValue < 0 then
raise Exception.Create('Invalid Enum Value!');
Include(TIntegerSet(Result), EnumValue);
EnumName := NextWord(P);
end;
end;
end.
Thanks Vincent. I seems that the new RTTI doesn’t provide an elegant solution as yet. I’m wondering if Spring4D has anything to assist? Thanks for your help.
Gerard
It may well do, the code I posted was written a long time ago - before the new Rtti code. The new rtti code is written using the old rtti as a base - so the older api is still quite valid to use.