复合(?)组件内的命名子组件

Named subcomponent inside compound(?) component

我主要想创建的是一个继承自 TScrollBox 的组件。该组件有一个 TGroupBox,里面有一个 TFlowPanel。我需要的是,当我双击该组件时,会出现一个类似于 TCollection 的编辑器,我可以在其中添加组件 (TFiltros),这些组件将成为 TFlowPanel 的子组件。问题是我想要命名这些组件,这样我就可以通过代码直接访问它们,有点像 TClientDataSet,您可以在其中添加字段,它们会出现在您的代码中。

我通过重写 GetChildren 并将其设为 return TFlowPanel 的子项,使其几乎可以正常工作。这也要求我让 TFiltros 的所有者成为他们所在的表格。它在结构面板中显示为子级(即使他们不是直接子级)并且还将它保存在 DFM 中,但是当我关闭表单并再次打开它,它无法从 DFM 加载数据,引发访问冲突。我不知道如何覆盖加载以正确设置子项。

任何关于如何解决这个问题的帮助,甚至是不同的想法都会非常好。我刚开始创建 Delphi 组件。

我当前的代码深受 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.

我终于成功了。它需要 TOwnedCollection and overriding GetChildren and GetParentComponent.

的组合

基本上我所学到的(如果我错了你可以纠正我)如下:

要在“结构”选项卡中显示一个组件,该组件的 Owner 必须是表单。所以第一件事就是用那个所有者创建TFiltro

GetParentComponent 定义组件将驻留在结构树中的位置,它不一定是实际的父级。所以第二件事是将 TFiltroGetParentComponent return 设为 TScrollBox 但将 实际父级 设置为 TFlowPanel.

现在,由于 TFiltro 的父级不再是表单,因此不会将其保存到 DFM,因为 TFlowPanel 是实际的父级但未定义为子组件。重写 TScrollBox 中的 GetChildren 并使其每隔 TFiltro 变为 return 解决了这个问题,它现在作为子项保存在 DFM 中。

但是现在,要从 DFM 中正确读回 TFiltro 并相应地重新设置,它必须是 TOwnedCollection 中某个项目中的 published 值,它本身就是 TScrollBox 中的一个发布值。然后,使 TCollectionItem 发布值的设置函数将 TFiltro 的父级定义为 TFlowPanel.

对我实现此目标帮助最大的文章可在 WayBack machine 中找到。