如何将面板分离并在单独的窗口中显示?

5

假设我有一个包含面板(以及许多其他控件)的表单A和一个空表单B。
我能否通过编程的方式将面板从表单A中分离并移动到表单B中(然后可能再次移回表单A)?

我知道可以更改面板的所有者,但是否可以在不同的表单之间使用?

更新:
经过一些谷歌搜索,我发现有一个ParentWindow属性。


1
ParentWindow 不起作用。请参阅文档中的此处:"如果 Parent 不是 nil(Delphi)或 NULL(C++),则设置 ParentWindow 没有效果。" Parent 不是 nil(它是 formA)。 - Ken White
4个回答

8
如其他人所指出的,未更改所有权的情况下更改控件的父窗口存在几个问题,如果控件上有多个子控件,则更改控件的所有者可能会很困难... 一种解决方法是使用一个框架。框架拥有其所有的子控件,因此你只需要更改框架的所有者和父级,其他所有内容都将随之而来。这种方法还允许你将所有事件处理程序和粘合代码放在同一个地方。N@

+1 因为解释了为什么 Frame 是最佳解决方案。显然,我在我的建议中没有完全做到这一点。;-) - NGLN

4

在考虑所有权时,否则表单A的销毁将导致您在表单B上的面板消失(即破坏),甚至更糟。

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

额外的引用可能有点傻:Form2.InsertedPanel和Panel1指向同一个对象,但从语义上讲,它更好。也许一个中央控制的变量更好。
更新:
我错误地认为RemoveComponent会级联到面板上的子控件。当然不会,只有从表单A中删除面板时,面板的所有子控件仍然属于表单A。因此,我添加了RemoveComponents例程,以将所有面板子控件的所有权移除。
请注意,此时面板的子控件没有所有者。但是,由于它们是面板的父控件,所以销毁面板将释放这些控件。因此,请确保面板有一个父控件,或者显式释放面板。
以上所有内容仅适用于设计时创建的面板,在表单上进行设计,这是我的假设。由于这种更改父项行为显然是所需的,因此您可能需要考虑在运行时完全实现它。为了保持对面板进行设计时的能力,建议创建一个Frame,在其中可以设计该面板,并在您的表单上跳转该Frame。

实际上,你并不完全正确。更改父级会改变谁负责销毁它的责任(父级负责 - 请参见我在答案中发布的文档链接)。因此,更改父级处理从Form1中删除组件并将其添加到Form2中。(这在我链接的维基条目中有具体描述 - 请参见注释。) - Ken White
@Ken Parent是所有者,这个信息从D7开始就有介绍了吗? - NGLN
2
@NGLN - 我检查了D3 'controls.pas','TControl.SetParent'中调用了'FParent.RemoveControl(Self)'和'AParent.InsertControl(Self)'。 - Sertac Akyuz
2
@Sertac @Ken 插入控件 <> 插入组件。在 D7 中,我销毁了表单 A,导致表单 B 上的面板被销毁。Owner <> Parent。 - NGLN
1
+1。如果面板确实是在设计时创建的,我认为你的答案比我的好得多。如果面板是在运行时构建的,那么我的答案更好。 :) 我会编辑我的答案以反映这一点,并留作将来参考。 - Ken White
显示剩余2条评论

4
您可以通过使用TForm来模拟面板和表单的外观,并在运行时将其停靠在留给该目的的空白面板中,以及通过相同的方式在运行时取消停靠。但您无法将TPanel取消停靠并使其显示为顶级窗体,但是您可以在代码中获取顶级窗体并进行停靠。为了获得所需的外观和功能,必须使用正确的工具(在本例中为TForm)。顺便提一下,像Toolbar 2000这样的组件库确实允许基于工具栏面板的浮动工具栏窗口,因此,如果您确实坚持所有设计时元素保留在一个表单中,请查看Toolbar 2000中的工作原理。它有很多代码用于在“取消停靠/浮动”模式下呈现工具栏,并处理将工具栏停靠到工具栏停靠区域中的鼠标驱动的操作。

2
如果面板和子组件是在运行时创建的,你只需将面板的 Parent 设置为 FormB :
Panel1.Parent := FormB;

请注意,在执行此操作之前,必须先创建FormB
如需更多信息,请参阅Delphi Wiki页面这里

只有在面板没有在窗体A的设计时创建时才适用。否则,窗体A将拥有该面板的所有权。 - NGLN
同意。已经修改以反映这一点。 - Ken White
很遗憾,面板上的控件配置相当复杂。在运行时构建它们需要相当多的工作量。 - Gabriel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接