在哪里释放动态分配的 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 作为其所有者动态创建它,都会发生这种情况。我目前使用包含TFormOnDestroy事件来调用包含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】:

您可以像这样使用TFrameWM_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 组件的对象?的主要内容,如果未能解决你的问题,请参考以下文章

动态内存

CH12 动态内存

动态内存和智能指针

动态内存——动态内存与智能指针

传递动态内存

类对象的动态分配以及释放