3

Given the following code snippet below, using GetPropValue(MyComponent,'MySubComponent.Prop1') raises an EPropertyError exception. How can I retrieve or set the values of SubProperties using GetPropValue / SetPropValue?

Type
  TMySubComponent = class(TInterfacedPersitent)
  private
    FProp1: Integer;
  published
    property Prop1: integer read FProp1 write FProp1;
  end;

  TMyComponent = class(TCompoent)
  private
    FMySubComponent : TMySubcomponent; 
  published
    property MySubComponent: TMySubComponent read FMySubComponent write FMySubComponent ;
  end;
iamjoosy
  • 3,299
  • 20
  • 30

2 Answers2

7

As Robert says the dot notation is not supported , but you can create easily a function to set or get a sub-property value using the RTTI. check this sample

{$APPTYPE CONSOLE}

uses
  Rtti,
  Classes,
  SysUtils;


Type
  TMySubComponent = class(TInterfacedPersistent)
  private
    FProp1: Integer;
  published
    property Prop1: integer read FProp1 write FProp1;
  end;

  TMyComponent = class(TComponent)
  private
    FMySubComponent : TMySubcomponent;
  published
    property MySubComponent: TMySubComponent read FMySubComponent write FMySubComponent ;
  end;



procedure SetObjValueEx(const ObjPath:string;AInstance:TObject;AValue:TValue);
Var
 c            : TRttiContext;
 Prop         : string;
 SubProp      : string;
 pm           : TRttiProperty;
 p            : TRttiProperty;
 Obj          : TObject;
begin
 Prop:=Copy(ObjPath,1,Pos('.',ObjPath)-1);
 SubProp:=Copy(ObjPath,Pos('.',ObjPath)+1);
 c := TRttiContext.Create;
 try
   for pm in c.GetType(AInstance.ClassInfo).GetProperties do
   if CompareText(Prop,pm.Name)=0 then
   begin
     p := c.GetType(pm.PropertyType.Handle).GetProperty(SubProp);
      if Assigned(p) then
      begin
        Obj:=pm.GetValue(AInstance).AsObject;
        if Assigned(Obj) then
          p.SetValue(Obj,AValue);
      end;
      break;
   end;
 finally
   c.Free;
 end;
end;


function GetObjValueEx(const ObjPath:string;AInstance:TObject):TValue;
Var
 c            : TRttiContext;
 Prop         : string;
 SubProp      : string;
 pm           : TRttiProperty;
 p            : TRttiProperty;
 Obj          : TObject;
begin
 Prop:=Copy(ObjPath,1,Pos('.',ObjPath)-1);
 SubProp:=Copy(ObjPath,Pos('.',ObjPath)+1);
 c := TRttiContext.Create;
 try
   for pm in c.GetType(AInstance.ClassInfo).GetProperties do
   if CompareText(Prop,pm.Name)=0 then
   begin
     p := c.GetType(pm.PropertyType.Handle).GetProperty(SubProp);
      if Assigned(p) then
      begin
        Obj:=pm.GetValue(AInstance).AsObject;
        if Assigned(Obj) then
          Result:=p.GetValue(Obj);
      end;
      break;
   end;
 finally
   c.Free;
 end;
end;

Var
  MyComp : TMyComponent;
begin
  try
     MyComp:=TMyComponent.Create(nil);
     try
       MyComp.MySubComponent:=TMySubComponent.Create;
       //Set the value of the property 
       SetObjValueEx('MySubComponent.Prop1',MyComp,777);
       //Get the value of the property 
       Writeln(Format('The value of MySubComponent.Prop1 is %d',[GetObjValueEx('MySubComponent.Prop1',MyComp).AsInteger]));
     finally
       MyComp.Free;
     end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.
RRUZ
  • 134,889
  • 20
  • 356
  • 483
  • Code is silent of SubComponent is Nil. – Robert Love Oct 04 '11 at 15:49
  • @RobertLove What do you mean? the line `if Assigned(Obj) then` check the subcomponent. – RRUZ Oct 04 '11 at 15:56
  • If SubComponent is nil and I trying to set a property of of that sub component the code does nothing. It might be wise to raise any exception as the code did not do anything and you expected it to do so. – Robert Love Oct 04 '11 at 16:03
  • @RobertLove , Indeed , this is just a basic sample. the code always can be improved. – RRUZ Oct 04 '11 at 16:09
4

The dot notation you used in your question is not supported.

You need to get the Value of the SubComponent, then perform the Set and Get on the individual properties.

var
  C: TRttiContext;
  MyComp : TMyComponent;
  MyCompType : TRttiInstanceType;
  MySubCompType : TRttiInstanceType;
  MySubComponentValue : TValue;
begin
  MyComp := TMyComponent.create(Self); 
  ...
  // RTTI.Pas Method
  MyCompType :=  c.GetType(TMyComponent.ClassInfo) as TRttiInstanceType;
  MySubCompType := c.GetType(TMySubComponent.ClassInfo) as TRttiInstanceType;
  MySubComponentValue := MyCompType.GetProperty('MySubComponent').GetValue(MyComp);

  if Not MySubComponentValue.IsEmpty then
  begin
      MySubCompType.GetProperty('Prop1').SetValue(MySubComponentValue.AsObject,43);
  end;

 //TypInfo.pas Method
 SubComp := GetObjectProp(MyComp,'MySubComponent');
 if Assigned(SubComp) then
 begin
    SetPropValue(SubComp,'Prop1',5);
    prop1Value := GetPropValue(SubComp,'Prop1');
 end;

end;

The TypInfo.pas method will only work with published properties, you can get the public properties with the RTTI.pas method.

Robert Love
  • 12,447
  • 2
  • 48
  • 80