【问题标题】:Change component properties using SetPropValue() and RTTI with Delphi Tokyo使用 SetPropValue() 和 RTTI 与 Delphi Tokyo 更改组件属性
【发布时间】:2023-12-24 03:53:02
【问题描述】:

我使用下面的代码在运行时创建的组件中使用RTTI 和 Delphi 10.2 Tokyo 设置属性,一切正常,因为示例的属性是TypeLine,因为我可以直接访问它。

Componente_cc 是一个可以用任何类实例化的变量,无论是TLabelTButtonTEdit...还是任何其他类。在下面的例子中,我将它实例化为 TLine.

Var 
    Componente_cc: TControl;

    procedure TfrmPrincipal.AlteraPropriedades;
    begin
        if IsPublishedProp(Componente_cc, 'LineType') then
          SetPropValue(Componente_cc, 'LineType', 'Diagonal');
    end; 

但是,我不明白当有子属性时怎么办,例如Stroke,它有Kind@ 987654331@CapDash 等。如何使用SetPropValue() 函数更改这些属性的值。我简化了示例代码以便更好地理解,但在我系统的一般上下文中,我需要使用RTTI,当然直接通过代码更改属性会很简单,但我确实需要RTTI

【问题讨论】:

    标签: delphi firemonkey rtti


    【解决方案1】:

    这类似于您的other RTTI issue,您正在通过RTTI 访问控件的TextSettings.Font 属性。这同样适用于任何嵌套属性,例如 Stroke.Color 等。

    对于每个嵌套的子属性,您必须获取包含对象,根据需要重复,直到您到达所需的子对象,然后您可以获取/设置根据需要设置其属性值。

    因此,在这种情况下,您必须使用GetObjectProp() 来获取Stroke 属性对象,然后您可以使用SetPropValue() 来设置该对象的属性。例如:

    uses
      ..., TypInfo;
    
    var 
      Componente_cc: TControl;
    
    procedure TfrmPrincipal.AlteraPropriedades;
    var
      Stroke: TObject;
    begin
      if IsPublishedProp(Componente_cc, 'Stroke') then
      begin
        Stroke := GetObjectProp(Componente_cc, 'Stroke');
        if Stroke <> nil then
          SetPropValue(Stroke, 'Color', ...);
      end;
    end; 
    

    或者,为了避免命名属性的双重 RTTI 查找:

    uses
      ..., TypInfo;
    
    var 
      Componente_cc: TControl;
    
    procedure TfrmPrincipal.AlteraPropriedades;
    var
      PropInfo: PPropInfo;
      Stroke: TObject;
    begin
      PropInfo := GetPropInfo(Componente_cc, 'Stroke', [tkClass]);
      if PropInfo <> nil then
      begin
        Stroke := GetObjectProp(Componente_cc, PropInfo);
        if Stroke <> nil then
          SetPropValue(Stroke, 'Color', ...);
      end;
    end; 
    

    请注意,在 Delphi 2010 中引入了更强大的Enhanced RTTI(此 RTTI 不仅限于发布的属性,就像旧式 RTTI 一样),例如:

    uses
      ..., System.Rtti;
    
    var 
      Componente_cc: TControl;
    
    procedure TfrmPrincipal.AlteraPropriedades;
    var
      Ctx: TRttiContext;
      Prop: TRttiProperty;
      Stroke: TObject;
    begin
      Ctx := TRttiContext.Create;
    
      Prop := Ctx.GetType(Componente_cc.ClassType).GetProperty('Stroke');
      if (Prop <> nil) and (Prop.PropertyType.TypeKind = tkClass) {and (Prop.Visibility = mvPublished)} then
      begin
        Stroke := Prop.GetValue(Componente_cc).AsObject;
        if Stroke <> nil then
        begin
          Prop := Ctx.GetType(Stroke.ClassType).GetProperty('Color');
          if (Prop <> nil) {and (Prop.Visibility = mvPublished)} then
            Prop.SetValue(Stroke, ...);
        end;
      end;
    end; 
    

    但是,一旦您可以访问更高级别的对象,最好直接访问子属性,例如:

    uses
      ..., TypInfo;
    
    var 
      Componente_cc: TControl;
    
    procedure TfrmPrincipal.AlteraPropriedades;
    var
      PropInfo: PPropInfo;
      Stroke: TStrokeBrush;
    begin
      PropInfo := GetPropInfo(Componente_cc, 'Stroke', [tkClass]);
      if PropInfo <> nil then
      begin
        Stroke := GetObjectProp(Componente_cc, PropInfo, TStrokeBrush) as TStrokeBrush;
        if Stroke <> nil then
          Stroke.Color := ...; // <-- no RTTI needed!
      end;
    end; 
    

    或者:

    uses
      ..., System.Rtti;
    
    var 
      Componente_cc: TControl;
    
    procedure TfrmPrincipal.AlteraPropriedades;
    var
      Ctx: TRttiContext;
      Prop: TRttiProperty;
      Stroke: TStrokeBrush;
    begin
      Ctx := TRttiContext.Create;
    
      Prop := Ctx.GetType(Componente_cc.ClassType).GetProperty('Stroke');
      if (Prop <> nil) and (Prop.PropertyType.TypeKind = tkClass) {and (Prop.Visibility = mvPublished)} then
      begin
        Stroke := Prop.GetValue(Componente_cc).AsObject as TStrokeBrush;
        if Stroke <> nil then
          Stroke.Color := ...; // <-- no RTTI needed!
      end;
    end;
    

    【讨论】:

    • 您好@Remy,当您说自 2010 年以来有更强大的 RTTI 时,指的是您的第二个示例?直接访问属性?
    • 我使用 RTTI 是因为它是我能找到的唯一解决方案,我的问题是有问题的软件类似于 IDE,用户添加控件并更改属性、调整大小等,所以组件在运行时被添加到表单中,它们可以是任何类,我试图做一个函数来添加所有类型的组件,另一个来获取属性,另一个来设置属性,所以我创建了TControl 类型的对象及其类,问题是在 TControl 中我无法直接访问所有属性,因此我在其中使用了 RTTI。
    • @Anderson:直接访问属性与RTTI无关,只是普通的面向对象编程。 PPropInfo 的使用并不新鲜,它一直都可用。 System.TypInfo 单元实现的旧式 RTTI 已被 Enhanced RTTI implemented by the System.Rtti unit 取代。
    • 我明白了,谢谢。不管怎样,我现在将改进代码并更好地研究这个主题。