How to implement drag and drop at design time #76

Question
I'm writing a component (descendant of TCustomControl) that owns a button and a panel. When the button is clicked, the panel will either show or hide. But now I want to be able to put other controls on the panel (in design-mode). Somehow this does not seem to work. I can put i.e. a button on the panel, and it seems that the button is owned by the panel, because I can no longer drag the button off the panel. But when I then view the form as text (Alt F12), there's no button. Somehow the dropped control is not added to the panel.

I misread your post first, not seeing that you want to put controls on the panel rather than the CustomControl. The component below allows this, but you have to be aware that this way you hand the panel over to the IDE, more or less, and your component user can do whatever with it, including removing the panel at design time without removing your control. I've tried to get it to be recreated in this case, but it looks a bit kludgy. This is the 2nd or 3rd time I'm posting a compound component like this, but I feel the solution isn't optimal.

unit CustomControl2;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;

type
  TCustomControl2 = class(TCustomControl)
  private
    fbutton: TButton;
    fPanel: TPanel;
    procedure TogglePanel(sender: TObject);
    procedure CreatePanel(AOwner: TComponent);
  protected
    procedure Loaded; override;
    procedure Notification(AComponent: TComponent; Operation: TOperation);
      override;
  public
    constructor Create(AOwner: TComponent); override;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TCustomControl2]);
end;

{ TCustomControl2 }

constructor TCustomControl2.Create(AOwner: TComponent);
begin
  inherited;
  fPanel := nil;
  if (csDesigning in ComponentState) and not
    (csReading in AOwner.ComponentState) then
    {this is true if the component is dropped on the form}
    CreatePanel(AOwner);
    {now the panel is part of the streaming system and will be created from
    the dfm file of the form in the future}
  fbutton := TButton.Create(self);
  {the button is owned by self, not part of streaming}
  fbutton.Parent := self;
  fbutton.SetBounds(0, 0, 100, 25);
  fbutton.Caption := 'Toggle Panel';
  fbutton.OnClick:=TogglePanel;
end;

procedure TCustomControl2.CreatePanel(AOwner: TComponent);
begin
  fPanel := TPanel.Create(AOwner);
  fPanel.Parent := self;
  fPanel.height := 200;
  fPanel.align := alBottom;
  fPanel.Name := 'MyPanel';
  {giving the panel a name and creating it with the form/ frame as owner
  makes it streamable to dfm file}
end;

procedure TCustomControl2.Loaded;
var
  i: Integer;
begin
  inherited;
  for i := 0 to ControlCount - 1 do
    if Controls[i].Name = 'MyPanel' then
    begin
      fPanel := TPanel(Controls[i]);
      {make fPanel point to the right control}
      break;
    end;
  {this is a kludgy try to recreate the panel in case the user removes it
  at design time. But it only kicks in when the dfm file is being loaded
  again, so e.g. by viewing the form as text, then as form again}
  if not assigned(fPanel) then
  if csDesigning in ComponentState then
  begin
    CreatePanel(Owner);
    {recreate it}
    ShowMessage('Control ''MyPanel'' has been recreated');
  end;
end;

procedure TCustomControl2.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if not (csDestroying in ComponentState) then
  if operation = opRemove then
  if AComponent.Name = 'MyPanel' then
  {someone removes the panel without removing our component}
  begin
    ShowMessage('Control ''MyPanel'' should not be removed');
    {we can't recreate it here right now...}
    fPanel := nil;
    {but we can prevent AVs}
    {Loaded recreates it the next time the dfm is being loaded}
  end;
end;

procedure TCustomControl2.TogglePanel(sender: TObject);
begin
  if assigned(fPanel) then
    fPanel.visible := not fPanel.visible;
end;

initialization

RegisterClass(TPanel);

end.
Original resource: The Delphi Pool
Author: Renate Schaaf
Added: 2009/08/12
Last updated: 2009/08/12