5

Let's say I have form A that contains a panel (with many other controls in it) and a form B that it is empty.
Can I programmatically detach the panel from form A and move it in form B (and maybe back to form A)?

I know that I can change the Owner of the panel but does it work between different forms?

Update:
After some Googling I see that there is a ParentWindow property.

Gabriel
  • 20,797
  • 27
  • 159
  • 293
  • 1
    `ParentWindow` won't work. See [this](http://docwiki.embarcadero.com/VCL/en/Controls.TWinControl.ParentWindow) in the docs: "Setting ParentWindow has no effect if Parent is not nil (Delphi) or NULL (C++)." `Parent` isn't nil (it's formA). – Ken White Jun 23 '11 at 23:39

4 Answers4

8

As noted by others, there are several problems with changing the parent window of a control without changing the ownership, and changing a controls owner can be difficult if it has multiple controls sitting on it...

One way around it is to use a frame instead. A frame owns all of it's sub-controls, so all you would need to do is change the owner and parent of the frame, and everything else will come along with it. This approach also allows you to keep all the event handlers and glue code in a single place as well.

N@

Nat
  • 5,414
  • 26
  • 38
  • +1 For explaining _why_ a Frame is the best solution. Apparently I somehow didn't quite manage that with my suggestion. ;-) – NGLN Jun 25 '11 at 08:55
4

You have to take ownership into account, otherwise the destruction of form A would lead to the disappearance (i.e. destruction) of your panel on form B, or worse.

type
  TForm2 = class(TForm)
  public
    InsertedPanel: TControl;  // or TPanel 

.

procedure RemoveComponents(AForm: TComponent; AControl: TWinControl);
var
  I: Integer;
begin
  for I := 0 to AControl.ControlCount - 1 do
  begin
    if AControl.Controls[I] is TWinControl then
      RemoveComponents(AForm, TWinControl(AControl.Controls[I]));
    if AControl.Controls[I].Owner = AForm then
      AForm.RemoveComponent(AControl.Controls[I]);
  end;
  AForm.RemoveComponent(AControl);
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  Form2.InsertedPanel := Panel1;
  Panel1.Parent := nil;
  RemoveComponents(Self, Panel1);
  Form2.InsertComponent(Form2.InsertedPanel); // < this is not necessary
  Form2.InsertedPanel.Parent := Form2;        //   as long as Parent is set
  Panel1 := nil;                              //   or if you free the panel
end;                                          //   manually

The extra reference may seem a bit silly: Form2.InsertedPanel and Panel1 point to the same object, but it's kind of semantically preferred. Maybe a central controlled variable is better.

Update:

I falsely assumed that RemoveComponent cascaded to the child controls on the panel. It doesn't, of course, so only removing the panel from form A would leave all the child controls of the panel still owned by form A. So I added the RemoveComponents routine to remove ownership from all the child controls of the panel.

Note that the child controls of the panel don't have an owner at this time. But since they are parented controls of the panel, destruction of the panel will free those controls. So be sure the panel has a parent, or free the panel explicitly.

All of the above only applies to a design time created panel, placed design time on the form, which was my assumption. Since this changing parents behaviour is apparently wanted or needed, you might want to consider to implement it completely at runtime. To keep the abbility to design the panel designtime, I suggest to create a Frame on which you can design that panel, and jump the Frame around your forms.

NGLN
  • 43,011
  • 8
  • 105
  • 200
  • Actually, you're not quite right. Changing the parent changes responsibility for who destroys it (Parent does - see the doc link I posted in my answer). Therefore, changing the Parent handles removing the component from Form1 and adding it to Form2. (It's specifically described in the wiki entry I linked - see the **Note**.) – Ken White Jun 23 '11 at 23:36
  • @Ken Parent being Owner, is that introduced somewhere since D7? – NGLN Jun 23 '11 at 23:48
  • @NGLN: Actually, I'm not sure. I don't have D7 installed any longer, and can't check. It is in XE, and I just checked D2010 and it's the same. I won't be able to check D2007 until later. – Ken White Jun 24 '11 at 00:28
  • 2
    @NGLN - I checked D3 'controls.pas', 'TControl.SetParent' has calls to 'FParent.RemoveControl(Self)' and 'AParent.InsertControl(Self)' – Sertac Akyuz Jun 24 '11 at 00:40
  • @Sertac: Thanks for that. I kind of thought it had to have been early on, or it would have changed too much. It's probably just the documentation that was finally changed. – Ken White Jun 24 '11 at 00:43
  • 2
    @Sertac @Ken InsertControl <> InsertComponent. Here in D7, I destroyed form A, which resulted in the destruction of the panel on form B. Owner <> Parent. – NGLN Jun 24 '11 at 00:51
  • 1
    +1. I think your answer is much better than mine if the panel is indeed created at design time. Mine is better if the panel is constructed at runtime instead. :) I'll edit mine to reflect that and leave it for future reference. – Ken White Jun 24 '11 at 01:40
3

You can easily have something appear as if it was a panel, and also as a form, by really using a TForm for what you would have used the panel for. Then dock the form at runtime into the place where you have a blank panel left for that purpose, and undock it at runtime, by the same manner.

You can't undock a TPanel and have it appear as a top-level form window, but you can take a top level form window and dock it in code. To get the appearance and functionality you want you must use the correct tools (TForm, in this case).

Incidentally, component libraries like Toolbar 2000 do allow floating toolbar windows based on toolbar panels, so if you really insist on having all the designtim elements remain in one form, at desigtime, you should look into how it works in Toolbar 2000. It has a lot of code in there to render the toolbar in "undocked/floating" mode, and to handle the mouse-driven docking and undocking of toolbars into toolbar docks.

Warren P
  • 65,725
  • 40
  • 181
  • 316
2

If the panel and child components are created at runtime, you can just set the Parent of the Panel to FormB:

Panel1.Parent := FormB;

Note that FormB has to have been created already before you can do this.

For more info, see the Delphi Wiki page here.

Ken White
  • 123,280
  • 14
  • 225
  • 444