在哪里释放动态分配的 TFrame 组件的对象?
Posted
技术标签:
【中文标题】在哪里释放动态分配的 TFrame 组件的对象?【英文标题】:Where to free dynamically allocated TFrame's components' objects? 【发布时间】:2013-02-09 04:47:03 【问题描述】:我有一个包含TFrame
的表单。 TFrame
包含一个动态填充的 ComboBox
。每个ComboBox
条目都有一个关联对象。在调用 TFrame
的重写析构函数时,ComboBox
中的项目已经被清除,而没有释放它们的关联对象。无论我是在设计器视图中将ComboBox
放在表单上,还是在代码中以nil 或TFrame
作为其所有者动态创建它,都会发生这种情况。我目前使用包含TForm
的OnDestroy
事件来调用包含TFrame
的清理过程。
有没有更好的方法不需要TFrame
的容器显式调用过程?理想情况下,动态添加到ComboBox
的对象应该在哪里被释放?
【问题讨论】:
我的建议是改变你的设计。不要让组合框拥有这些对象。让框架在您喜欢的任何容器中拥有它们,例如TObjectList<TMyObject>
。然后您可以在框架的析构函数中销毁该容器。
【参考方案1】:
你说当TFrame的析构函数被调用时,ComboBox的Items已经被清除了。情况并非如此,ComboBox 项目永远不会被清除。当 Items 被 ComboBox 销毁时,它们的计数仅为 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;
【讨论】:
@kobik - 谢谢。我也喜欢你的,因为它与破坏形式分离。【参考方案2】:您可以像这样使用TFrame
的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
所有者表单在TAppBaseForm.FormDestroy
事件处理程序上被销毁。此时 ComboBox 项仍然有效(如 Sertac Akyuz 的 answer 所述)。
【讨论】:
【参考方案3】:您的问题并不是很有用,因为 - 一般而言 - 不鼓励在 GUI 控件中 存储 数据(或您的情况下的对象)。另请参阅 David 关于如何更改您的设计的评论。
让这个问题回答起来很有趣的是,组合框直接作为表单的子项与作为表单的另一个子项(在本例中为您的框架)的子项之间的区别。显然,在调用该框架的析构函数之前,组合框项目已销毁。然后可以探索的明显替代方案是:覆盖 Frame.BeforeDestruction
、覆盖 Frame.DestroyWindowHandle
、覆盖 Frame.DestroyWnd
或在覆盖的 Frame.WndProc
中捕获 WM_DESTROY
,但在项目已经消失之前,它们都不会被调用。
接下来要尝试对组合框重复此操作。事实证明,当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.
现在,回答您的问题:“这是更好的方法吗?”
是的,因为它保证了对象在框架级别的破坏。换句话说:您不必记住为每个实例分别处理这个问题。
不,不是,因为此解决方案要求在任何情况下都允许释放组合框中的对象,这将使用限制在不必要的额外边界。
那么,这个答案有用吗?好吧,如果它阻止您使用当前的方法,那么它就是。
此外,我还通过在包含表单 OnDestroy
处理程序中将框架的 Parent
属性设置为 nil 找到了另一种选择:
procedure TForm2.FormDestroy(Sender: TObject);
begin
Frame1.Parent := nil;
end;
在这种情况下,您可以安全地销毁存储在框架析构函数内的组合框中的对象。但是这个解决方案比你现在的解决方案更糟糕,因为它不是描述性的。那么Frame1.FreeComboObjects
就好多了。
【讨论】:
你认为在 ComponentState 中测试 csDestroying 可以代替 FRecreating 吗? @Sertac 是的,它更简洁,更具描述性。改了。【参考方案4】:在析构函数中释放 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;
【讨论】:
以上是关于在哪里释放动态分配的 TFrame 组件的对象?的主要内容,如果未能解决你的问题,请参考以下文章