2
votes

I would like to create a component with the structure as bellow:

type
  TCustomMyComp = class;
  TMyComp = class;
  TCustomSqlCommands = class;
  TSqlCommands = class;
  TFields = class;
  TFieldsItem = class;
  TFieldsSqlCommands = class;

 TCustomMyComp = class(TComponent)
  private
    FFields: TFields;
    FSqlCommands: TSqlCommands;
    procedure SetFields(Value: TFields);
    procedure SetSqlCommands(Value: TSqlCommands);
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property Fields: TFields read FFields write SetFields;
    property SqlCommands: TSqlCommands read FSqlCommands write SetSqlCommands;
  end;

TCustomSqlCommands = class(TPersistent)
  private
    FOwner: TCustomMyComp;
    FSelect: TStrings;
    FInsert: TStrings;
    FUpdate: TStrings;
    FDelete: TStrings;
    procedure SetSql(Index: Integer; Value: TStrings);
  public
    constructor Create(AOwner: TCustomMyComp); virtual;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
  published
    property Select: TStrings index 0 read FSelect write SetSql;
    property Insert: TStrings index 1 read FInsert write SetSql;
    property Update: TStrings index 2 read FUpdate write SetSql;
    property Delete: TStrings index 3 read FDelete write SetSql;
  end;

[ComponentPlatformsAttribute(pidWin32 or pidWin64)]
  TMyComp = class(TCustomMyComp)
  published
    property Fields;
    property SqlCommands;
  end;

TSqlCommands = class(TCustomSqlCommands)
  published
    property Insert;
    property Update;
    property Delete;
  end;

//------- Fields -----------

  TFields = class(TCollection)
  private
    FOwner: TCustomMyComp;
    function GetItem(Index: Integer): TFieldsItem;
    procedure SetItem(Index: Integer; Value: TFieldsItem);
  protected
    function GetOwner: TPersistent; override;
    procedure Update(Item: TCollectionItem); override;
  public
    constructor Create(AOwner: TCustomMyComp);
    function Add: TFieldsItem;
    function Owner: TCustomMyComp;
  end;

  TFieldsItem = class(TCollectionItem)
  private
    FOwner: TCustomMyComp;
    FSqlCommands: TFieldsSqlCommands;
    procedure SetSqlCommands(Value: TFieldsSqlCommands);
  protected
    function GetDisplayName: String; override;
    procedure SetIndex(Value: Integer);  override;
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
  published
    property SqlCommands: TFieldsSqlCommands read FSqlCommands write SetSqlCommands;
  end;

  TFieldsSqlCommands = class(TCustomSqlCommands)
  published
    property Select;
    property Insert;
  end;

I face 2 issues:

  1. Property SqlCommands: TSqlCommands contain all properties that were defined under TCustomSqlCommands even I defined to published only Insert,Update,Delete.

  2. Property Fileds->SqlCommands: TFieldsSqlCommands contain NO properties even I defined to published only Select,Insert.

What am I doing wrong?

2

2 Answers

2
votes
  1. The typical design pattern is to have a TCustom... class define its properties as either public or protected, but never as published. This way, a derived class can decide which properties it wants to promote to published as needed. This would allow TSqlCommands to not publish the Select property, and TFieldsSqlCommands to not publish the Update and Delete properties.

  2. Your Fields property is a TFields object (BTW, the VCL already has a TFields class in the DB unit, so you should consider renaming your class to something more unique). Your TFields class does not have an SqlCommands property, it is a property of TFieldsItem instead. So you need to access one of your field items in order to reach its SqlCommands property.

    However, although TFields is a collection of TFieldsItem objects, you have not declared a property to expose direct access to your TFieldsItem object pointers (although you have declared getter/setter methods for such a property). You are inheriting the base Items[] property from TCollection, and that property exposes TCollectionItem object pointers, which you would have to type-cast to TFieldItem. So, you should add your own property (name it whatever you want) to your TFields class to handle that type-casting:

    TFields = class(TCollection)
    private
      ...
      function GetField(Index: Integer): TFieldsItem;
      procedure SetField(Index: Integer; Value: TFieldsItem);
      ...
    public
      ...
      property Fields[Index: Integer]: TFieldsItem read GetField write SetField default;
    end;
    

    BTW, since TFields has an Owner, you should consider deriving it from TOwnedCollection instead of TCollection directly.

1
votes

Are we talking about visibility in code? If yes, then it is right that you see all properties as public, as published has the same visibility. The only difference between those two is that published properties are included in RTTI so they can be seen in the Object Inspector. But in code you can access public and published properties equally.

So if your first problem is from the coding side, this is correct behavior.

Regarding your second problem:

If you access the Fields property from a TMyCustomComp instance, you do not directly have an SqlCommands property, as you only have access to the TFields object, which inherits from TCollection. You first have to access one of the items, if neccessary cast it to TFieldsItem and then you can access its SqlCommands property.

In your TCustomSqlCommands, you already have those properties published, and in TFieldsSqlCommands you publish them again. I do not know if that is a problem, but maybe you can try making them public in TCustomSqlCommands and keep the published ones in TFieldsSqlCommands. However, you still should be able to access them all in code due to their visibility.