我有一个包含TFrame
的表单。 TFrame
包含动态填充的ComboBox
。每个ComboBox
条目都有一个关联的对象。当调用TFrame
的被覆盖的析构函数时,ComboBox
中的项目已被清除,而不释放其关联的对象。无论我是在设计器视图中删除表单上的ComboBox
,还是在代码中使用nil或TFrame
作为其所有者动态创建它,都会发生这种情况。我目前使用包含OnDestroy
的{{1}}事件来调用包含TForm
的清理过程。
有没有更好的方法不需要TFrame
容器的显式过程调用?理想情况下,动态添加到TFrame
的对象应该被释放吗?
答案 0 :(得分:7)
你说当调用TFrame的析构函数时,ComboBox的Items已被清除。事实并非如此,ComboBox项目永远不会被清除。当ComboBox销毁Items时,它们的计数仅为0.
退出应用程序并且VCL销毁包含框架和ComboBox的表单时,操作系统也会销毁本机ComboBox控件,因为它被放置在被销毁的窗口中。当您稍后访问项目以便能够在帧析构函数中释放对象时,VCL必须重新创建一个本机ComboBox控件,其项目计数为0.。
我建议的解决方案很简单。不要将框架释放到框架中,而是在表单的OnDestroy
事件中销毁框架。那将是在表单的底层窗口被销毁之前,因此您将能够访问您的对象。
表格单位
procedure TMyForm.FormDestroy(Sender: TObject);
begin
MyFrame.Free;
end;
帧单位
destructor TMyFrame.Destroy;
var
i: Integer;
begin
for i := 0 to ComboBox1.Items.Count - 1 do
ComboBox1.Items.Objects[i].Free;
inherited;
end;
答案 1 :(得分:6)
您可以像这样使用TFrame
的{{1}}处理程序:
WM_DESTROY
另一种选择是为整个应用程序创建基本祖先unit Unit2;
interface
uses
Windows, Messages, SysUtils, Classes, Controls, Forms, StdCtrls;
type
TFrame1 = class(TFrame)
ComboBox1: TComboBox;
private
procedure WMDestroy(var Msg: TWMDestroy); message WM_DESTROY;
procedure FreeComboBoxItems;
public
constructor Create(AOwner: TComponent); override;
end;
implementation
{$R *.dfm}
constructor TFrame1.Create(AOwner: TComponent);
begin
inherited;
// Add some object items to the ComboBox
ComboBox1.AddItem('a', TButton.Create(nil));
ComboBox1.AddItem('b', TMemoryStream.Create);
ComboBox1.AddItem('c', TList.Create);
end;
procedure TFrame1.WMDestroy(var Msg: TWMDestroy);
begin
// Make sure the TFrame is actually destroying - not recreated
if (csDestroying in ComponentState) then
FreeComboBoxItems;
inherited;
end;
procedure TFrame1.FreeComboBoxItems;
var
I: Integer;
begin
OutputDebugString('TFrame1.FreeComboBoxItems');
with Self.ComboBox1 do
for I := 0 to Items.Count - 1 do
begin
OutputDebugString(PChar(Items.Objects[I].ClassName + '.Free'));
Items.Objects[I].Free;
end;
end;
end.
类和TAppBaseForm
,并将所有表单派生为TAppBaseFrame
,将所有帧派生为TAppBaseForm
。
这样,TAppBaseFrame
可以通知所有孩子TAppBaseForm
所有者表单在TAppBaseFrame
事件处理程序中被销毁。此时,ComboBox项目仍然有效(如Sertac Akyuz的answer所述)。
答案 2 :(得分:3)
您的问题并不真正有用,因为 - 一般来说 - 不鼓励在GUI控件中存储数据(或您的案例中的对象)。另请参阅David关于如何更改设计的评论。
是什么使得问题类似于有趣的回答虽然是组合框直接作为表单的孩子和作为表单的另一个孩子的孩子(在这种情况下是你的框架)之间的区别。显然,在调用该框架的析构函数之前,组合框项目是销毁。然后,明显的探索替代方案是:覆盖Frame.BeforeDestruction
,覆盖Frame.DestroyWindowHandle
,覆盖Frame.DestroyWnd
或在覆盖的WM_DESTROY
中捕获Frame.WndProc
,但不会调用它们在物品已经消失之前。
接下来要尝试的是为组合框重复此操作。事实证明,当WM_DESTROY
到达组合框时,项目仍然存在。但是,当控件真正被销毁时,要小心捕获该消息,因为VCL可能经常重新创建一个组合框。使用TComboBox
的插入类实现它,如下所示:
unit Unit2;
interface
uses
Windows, Messages, Classes, Controls, Forms, StdCtrls;
type
TComboBox = class(StdCtrls.TComboBox)
protected
procedure WndProc(var Message: TMessage); override;
end;
TFrame1 = class(TFrame)
ComboBox1: TComboBox;
end;
implementation
{$R *.dfm}
{ TComboBox }
procedure TComboBox.WndProc(var Message: TMessage);
var
I: Integer;
begin
if (Message.Msg = WM_DESTROY) and (csDestroying in ComponentState) then
for I := 0 to Items.Count - 1 do
Items.Objects[I].Free;
inherited WndProc(Message);
end;
end.
现在,回答你的问题:“这是一种更好的方式吗?”
是的,因为它确保了对象在帧级别的破坏。换句话说:你不必记住分别为每个实例处理这个问题。
并非它不是,因为这个解决方案要求允许在任何限制使用的情况下释放组合框中的对象到不必要的额外边界。
那么,这个答案有用吗?好吧,如果它阻止你使用当前的方法,那就是。
此外,我还通过在包含的Parent
处理程序中将框架的OnDestroy
属性设置为nil来找到另一种替代方法:
procedure TForm2.FormDestroy(Sender: TObject);
begin
Frame1.Parent := nil;
end;
在这种情况下,您可以安全地销毁框架析构函数中组合框中存储的对象。但是这个解决方案甚至比你现在的解决方案更糟糕,因为它不具有描述性。然后Frame1.FreeComboObjects
要好得多。
答案 3 :(得分:0)
释放析构函数中的Combobox.Items.Objects为时已晚。 因此,根据以前的答案,这样做更好,更安全:
procedure TMyFrame.ClearCBObjects;
var
i: Integer;
begin
for i := 0 to ComboBox1.Items.Count - 1 do
ComboBox1.Items.Objects[i].Free;
end;
destructor TMyFrame.Destroy;
begin
//Free none Component objects
inherited;
end;
destructor TMyForm.Destroy;
begin
MyFrame.ClearCBObjects;
inherited;
end;