Search code examples
windowsdelphidelphi-7

Moving controls between Delphi components


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.


Solution

  • 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