Home > Net >  Named subcomponent inside compound(?) component
Named subcomponent inside compound(?) component

Time:04-29

What I'm basically trying to create is a component that inherits from TScrollBox. That component has a TGroupBox and inside it a TFlowPanel. What I need is when I double click this component, a TCollection-like editor appears where I can add components (TFiltros) that will be children of that TFlowPanel. The problem is that I want those components to be named, such that I can directly access them via code, kinda like a TClientDataSet, where you add fields and they appear in your code.

I've managed to make it almost work by overriding GetChildren and making it return the children of the TFlowPanel. That also required me to make TFiltros's owner be the Form which they are in. It shows in the Structure panel as children (even tho they are not direct children) and also saves it in the DFM, but when I close the form and open it again, it fails to load the data back from the DFM, throwing an Access Violation. I have no idea how to override the loading to properly set the children.

Structure view

Any help in how I can fix that, or even different ideas would be really nice. I'm new to creating Delphi components.

My current code which is heavily inspired in this question:

unit uFiltros;

interface

uses
  System.SysUtils, System.Classes, Vcl.Controls, Vcl.ExtCtrls, Forms, StdCtrls, 
  ClRelatorio, Math, DesignEditors, DesignIntf, System.Generics.Collections;

type
  TFiltrosEditor = class(TComponentEditor)
    procedure ExecuteVerb(Index: Integer); override;
    function GetVerb(Index: Integer): String; override;
    function GetVerbCount: Integer; override;
  end;

  TFiltros = class(TScrollingWinControl)
  private
    FChilds: TList<TComponent>;
    FGroupBox: TGroupBox;
    FFlowPanel: TFlowPanel;
    FWidth: Integer;
    procedure OnFlowPanelResize(Sender: TObject);
    procedure SetWidth(AWidth: Integer);
  public
    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
    function GetChildOwner: TComponent; override;
    constructor Create(AOwner: TComponent); override;
    property Childs: TList<TComponent> read FChilds;
  published
    property Width: Integer read FWidth write SetWidth;
  end;

  TClFiltro = class(TFiltro)
  private
    FFiltros: TFiltros;
  protected
    procedure SetParent(AParent: TWinControl); override;
  public
    constructor Create(AOwner: TComponent; AFiltros: TFiltros); reintroduce;
    function GetParentComponent: TComponent; override;
    function HasParent: Boolean; override;
    property Parent: TWinControl write SetParent;
  end;

  TFiltroItem = class(TCollectionItem)
  private
    FFiltro: TClFiltro;
  protected
    function GetDisplayName: String; override;
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
  published
    property Filtro: TClFiltro read FFiltro write FFiltro;
  end;

  TFiltrosCollection = class(TOwnedCollection)
  private
    FDesigner: IDesigner;
  public
    property Designer: IDesigner read FDesigner write FDesigner;
  end;

procedure Register;

implementation

uses Dialogs, ClFuncoesBase, Vcl.Graphics, ColnEdit;

procedure Register;
begin
  RegisterClass(TClFiltro);
  RegisterNoIcon([TClFiltro]);
  RegisterComponents('Cl', [TFiltros]);
  RegisterComponentEditor(TFiltros, TFiltrosEditor);
end;

{ TFiltroItem }

constructor TFiltroItem.Create(Collection: TCollection);
begin
   inherited;
   if Assigned(Collection) then
   begin
      FFiltro        := TClFiltro.Create(TFiltros(Collection.Owner).Owner, TFiltros(Collection.Owner));
      FFiltro.Name   := TFiltrosCollection(Collection).Designer.UniqueName(TClFiltro.ClassName);
      FFiltro.Parent := TFiltros(Collection.Owner).FFlowPanel;
      FFiltro.Margins.Top      := 1;
      FFiltro.Margins.Bottom   := 1;
      FFiltro.AlignWithMargins := True;
      //FFiltro.SetSubComponent(True);
   end;
end;

destructor TFiltroItem.Destroy;
begin
   FFiltro.Free;
   inherited;
end;

function TFiltroItem.GetDisplayName: String;
begin
   Result := FFiltro.Name;
end;

{ TFiltros }

constructor TFiltros.Create(AOwner: TComponent);
begin
   inherited;
   FChilds := TList<TComponent>.Create;
   // Configurações ScrollBox
   Align      := TAlign.alRight;
   AutoScroll := False;
   AutoSize   := True;

   //Configurações GroupBox
   FGroupBox := TGroupBox.Create(Self);
   FGroupBox.Parent     := Self;
   FGroupBox.Caption    := ' Fil&tros ';
   FGroupBox.Font.Style := [fsBold];

   //Configurações FlowPanel
   FFlowPanel := TFlowPanel.Create(FGroupBox);
   FFlowPanel.Parent     := FGroupBox;
   FFlowPanel.Top        := 15;
   FFlowPanel.Left       := 2;
   FFlowPanel.AutoSize   := True;
   FFlowPanel.FlowStyle  := TFlowStyle.fsRightLeftTopBottom;
   FFlowPanel.Caption    := '';
   FFlowPanel.OnResize   := OnFlowPanelResize;
   FFlowPanel.BevelOuter := TBevelCut.bvNone;
end;

function TFiltros.GetChildOwner: TComponent;
begin
   Result := FFlowPanel;
end;

procedure TFiltros.GetChildren(Proc: TGetChildProc; Root: TComponent);
var I: Integer;
begin
//  inherited;
  for I := 0 to FChilds.Count - 1 do
    Proc(TComponent(FChilds[I]));
end;

procedure TFiltros.OnFlowPanelResize(Sender: TObject);
begin
   FGroupBox.Width     := FFlowPanel.Width   4;
   FGroupBox.Height    := Max(FFlowPanel.Height   17, Height);
   VertScrollBar.Range := FGroupBox.Height;
   FWidth := FFlowPanel.Width;
end;

procedure TFiltros.SetWidth(AWidth: Integer);
begin
   FFlowPanel.Width := AWidth;
   FWidth := FFlowPanel.Width;
   OnFlowPanelResize(Self);
end;

{ TFiltrosEditor }

procedure TFiltrosEditor.ExecuteVerb(Index: Integer);
var LCollection: TFiltrosCollection;
    I: Integer;
begin
  LCollection := TFiltrosCollection.Create(Component, TFiltroItem);
  LCollection.Designer := Designer;
  for I := 0 to TFiltros(Component).Childs.Count - 1 do
    with TFiltroItem.Create(nil) do
    begin
      FFiltro    := TClFiltro(TFiltros(Component).Childs[I]);
      Collection := LCollection;
    end;
  ShowCollectionEditorClass(Designer, TCollectionEditor, Component, LCollection, 'Filtros');
end;

function TFiltrosEditor.GetVerb(Index: Integer): String;
begin
   Result := 'Editar filtros...';
end;

function TFiltrosEditor.GetVerbCount: Integer;
begin
   Result := 1;
end;

{ TClFiltro }

constructor TClFiltro.Create(AOwner: TComponent; AFiltros: TFiltros);
begin
   inherited Create(AOwner);
   FFiltros := AFiltros;
end;

function TClFiltro.GetParentComponent: TComponent;
begin
   Result := FFiltros;
end;

function TClFiltro.HasParent: Boolean;
begin
   Result := Assigned(FFiltros);
end;

procedure TClFiltro.SetParent(AParent: TWinControl);
begin
   if Assigned(AParent) then
      FFiltros.FChilds.Add(Self)
   else
      FFiltros.FChilds.Remove(Self);
   inherited;
end;

end.

CodePudding user response:

I've finally managed to do it. It required a combination of TOwnedCollection and overriding GetChildren and GetParentComponent.

Basically what I've learned (and you can correct me if I'm wrong), is the following:

For a component to be shown in the Structure tab at all, the Owner of that component has to be the form. So the first thing was to create TFiltro with that owner.

GetParentComponent defines where in the Structure tree the component is going to reside in, it doesn't necessarily have to be the actual parent. So the second thing was to make GetParentComponent of the TFiltro return the TScrollBox but set the actual parent to be the TFlowPanel.

Now, as the parent of TFiltro no longer is the form, it won't save it to the DFM, because TFlowPanel is the actual parent but is not defined as a subcomponent. Overriding GetChildren in the TScrollBox and making it return every TFiltro solves this, and it is now saved in the DFM as a child.

But now, for the TFiltro to be properly read back from the DFM and be set again accordingly, it has to be a published value in an item inside the TOwnedCollection, which itself is a published value in the TScrollBox. Then, make the TCollectionItem published value's set function define the parent of the TFiltro to be the TFlowPanel.

The article which helped me the most in achieving this is available in the WayBack machine.

  • Related