1
votes

I´m trying to create a Delphi non-visual component that can hold some visual components.

In design time I create a custom TPanel, so I can put my visual components in it and then I try to get this controls from the TPanel and store them in another component.

This is my custom panel

  TDesignTimePanel = class(TPanel)
  private
    FPanel: TPanelDialogo;
  public
    constructor Create(AOwner: TComponent); override;
    destructor  Destroy; override;

    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
    function  GetChildOwner: TComponent; override;
  end

The method GetChildren does nothing, as I don't want to write this panel in a traditional way in the DFM file. The method GetChildOwner returns the TPanelDialogo where I want the visual controls to be stored.

And this is the component where I want to store the controls from the TDesignTimePanel

  TPanelDialogo = class(TComponent)
  private
    FDesignPanel: TDesignTimePanel;
    procedure VolcarFrameEnLista();
  public
    constructor Create(AOwner: TComponent); override;
    destructor  Destroy; override;
    function  CrearPanel(AOwner: TComponent): TPanel;
    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
    function  GetChildOwner: TComponent; override;
  end;

I create the custom panel this way

function TPanelDialogo.CrearPanel(AOwner: TComponent): TPanel;
var
  i: integer;
  Componente : TControl;
begin
  if FDesignPanel = nil then
  begin
    FDesignPanel := TDesignTimePanel.Create(self);
    FDesignPanel.AsociarPanel( self );
  end;

  FDesignPanel.Name := Name + '_frame';
  FDesignPanel.Left := FX;
  // some other config
  FDesignPanel.Parent := Owner as TWinControl;

  FDesignPanel.Show;

  Result := FDesignPanel;
end;

So my GetChildren method does the following, where VolcarFrameEnLista is the method where I take the controls from the TDesignTimePanel object and stores them in the TPanelDialogo (FListaComponentes is a TComponentList)

procedure TPanelDialogo.GetChildren(Proc: TGetChildProc; Root: TComponent);
var
  i: integer;
  OwnedComponent: TComponent;
begin
  if FDesignPanel <> nil then
  begin
    VolcarFrameEnLista();
    if Root = Self then
      for i := 0 to self.FListaComponentes.Count - 1 do
      begin
        OwnedComponent := FListaComponentes.Items[i];
        Proc(OwnedComponent);
      end;
  end;
end;

procedure TPanelDialogo.VolcarFrameEnLista( );
var
  i: integer;
  Componente: TControl;
begin
  for i := FDesignPanel.ControlCount - 1 downto 0 do
  begin
    Componente := FDesignPanel.Controls[i];
    if Pos( self.Name + '_', Componente.Name ) = 0 then
    begin
      Componente.Name := self.Name + '_' + Componente.Name;
    end;
    Componente.Parent := nil;
    if FListaComponentes.IndexOf(Componente) < 0 then
    begin
      FListaComponentes.Add( Componente );
    end;
  end;
end;

I want my DFM to have something like this:

object Form1: TForm1
  object PanelDialogo1: TPanelDialogo
    Left = 712
    // ...
    object PanelDialogo1_Label1: TLabel
      Left = 88
      // ..
    end
    object PanelDialogo1_Label2: TLabel
      Left = 40
      // ..
    end
  end
end

But I´m getting something like this

object Form1: TForm1
  object PanelDialogo1: TPanelDialogo
    Left = 712
    // ...
  end
  object PanelDialogo1_Label1: TLabel
    Left = 88
    // ..
  end
  object PanelDialogo1_Label2: TLabel
    Left = 40
    // ..
  end
end

What should I do so the TPanelDialogo takes "ownership" of the components drawn on the TDesignTimePanel.

1
InsertComponent. Doesn't look quite right at first sight though...Sertac Akyuz
Already tried that. InsertComponent fails: "A component named ... already exists". But if I try RemoveComponent then the component is destroyed.Héctor C.
There's no alternative, you have to insert into the components array for the ownership to change. Name must be unique, see docs.Sertac Akyuz
What is the actual problem you are trying to solve? Why would you want to store visual components as children of a non visual component? Delphi’s dfm storage works with an hiarchy based on parent/child for visual components. Non visual components always have the form as a parent/owner and cannot have children.R. Hoek
@R.Hoek - I agree, and have stated so, that the design looks questionable but dfm tree is not that much strict. Menu items, f.i., override Get|SetParentComponent to modify the behavior you describe.Sertac Akyuz

1 Answers

1
votes

I finally managed to solve my problem.

What I needed was to overwrite the GetChildren method on my parent object so I can get all the elements in the temporary panel into a TComponentList. Then I write each element of this list into the DFM file.

When reading the DFM file I get this elements in the TPanelDialogo.Components property, but storing this elements here got me trouble because of the duplicate control from Delphi environment. So on the Loaded method I put all these components into the TComponentList again.

Here´s the code

type

  TPanelDialogo = class;

  // especialización de Frame para pruebas
  TDesignTimePanel = class(TPanel)
  public
    constructor Create(AOwner: TComponent); override;
    destructor  Destroy; override;
    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
  end;

  TPanelDialogo = class(TComponent)
  private
    FDesignPanel: TDesignTimePanel;
    FGENPant: TGENPant;

    FListaComponentes : TComponentList;

    procedure CerrarPanel;
    procedure VolcarFrameEnLista();
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    function CrearPanel(AOwner: TComponent): TPanel;
    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
    function GetChildOwner: TComponent; override;
    procedure Loaded; override;

  published
    property ListaComponentes: TComponentList read FListaComponentes;
  end;

procedure Register;

implementation

uses
  ToolsApi,
  SysUtils, Graphics,
  Dialogs, StdCtrls,
  ComponentesGEN;

  { TDesignTimePanel }

constructor TDesignTimePanel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
end;

destructor TDesignTimePanel.Destroy;
begin
  inherited;
end;

procedure TDesignTimePanel.GetChildren(Proc: TGetChildProc; Root: TComponent);
begin
  exit;
end;

constructor TPanelDialogo.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  FListaComponentes := TComponentList.Create(True);
end;

destructor TPanelDialogo.Destroy;
begin
  inherited Destroy;
end;

procedure TPanelDialogo.VolcarFrameEnLista( );
var
  i: integer;
  Componente: TControl;
  OwnerName, ParentName: string;
begin
  // recorrer el frame y rescatar sus componentes
  if FDesignPanel = nil then
    exit;
  for i := FDesignPanel.ControlCount - 1 downto 0 do
  begin
    Componente := FDesignPanel.Controls[i];
    if Componente.Owner <> nil then
      OwnerName := Componente.Owner.Name;
    if Componente.Parent <> nil then
      ParentName := Componente.Parent.Name;
    if Pos( self.Name + '_', Componente.Name ) = 0 then
    begin
      Componente.Name := self.Name + '_' + Componente.Name;
    end;
    if FListaComponentes.IndexOf(Componente) < 0 then
    begin
      FListaComponentes.Add( Componente );
    end;
  end;
end;

procedure TPanelDialogo.CerrarPanel;
begin
  if FDesignPanel = nil then Exit;

  FDesignPanel.Visible := false;
end;

function TPanelDialogo.GetChildOwner: TComponent;
begin
  Result := self;
end;

procedure TPanelDialogo.GetChildren(Proc: TGetChildProc; Root: TComponent);
var
  i: integer;
  OwnedComponent: TComponent;
begin
  if FDesignPanel <> nil then
  begin
    VolcarFrameEnLista();
    for i := 0 to self.FListaComponentes.Count - 1 do
    begin
      OwnedComponent := FListaComponentes.Items[i];
      Proc(OwnedComponent);
    end;
  end;
end;

function TPanelDialogo.CrearPanel(AOwner: TComponent): TPanel;
var
  i: integer;
  Componente : TControl;
begin
  if FDesignPanel = nil then
  begin
    FDesignPanel := TDesignTimePanel.Create(self);
    FDesignPanel.AsociarPanel( self );
  end;

  FDesignPanel.Name := Name + '_frame';
  // ...

  try
    for i := 0 to FListaComponentes.Count - 1 do
    begin
      Componente := FListaComponentes.Items[i] as TControl;
      Componente.Parent := FDesignPanel;
    end;
  finally
    FDesignPanel.Parent := Owner as TWinControl;
  end;

  FDesignPanel.Visible := true;

  Result := FDesignPanel;
end;

procedure TPanelDialogo.Loaded;
var
  i: integer;
  OwnedComponent: TComponent;
begin
  inherited;  
  for i := 0 to self.ComponentCount - 1 do
  begin
    OwnedComponent := self.Components[i];
    self.FListaComponentes.Add(OwnedComponent);
  end;  
  for i := self.ComponentCount - 1 downto 0 do
  begin
    OwnedComponent := self.Components[i];
    self.RemoveComponent(OwnedComponent);
  end;
  self.FLoaded := true;
end;

This is what is shown at Design Time: enter image description here

And this is the DFM of the form

object Form1: TForm1
  ...
  object PanelDialogo1: TPanelDialogo
    ...
    object PanelDialogo1_Label2: TLabel
      Caption = 'Another label right here'
    end
    object PanelDialogo1_Label1: TLabel
      Caption = 'A label in the top of the panel'
    end
    object PanelDialogo1_Edit1: TEdit
      Text = 'Write something here...'
    end
    object PanelDialogo1_Panel1: TPanel
      object PanelDialogo1_Button1: TButton
        Caption = 'OK'
      end
    end
    object PanelDialogo1_Label3: TLabel
      Caption = 'Some label just here'
    end
  end
end