7

我有一个包含TFrame. TFrame包含一个ComboBox动态填充的。每个ComboBox条目都有一个关联的对象。到调用重写的析构函数时,已经清除TFrame了 中的项目,而没有释放它们的关联对象。ComboBox无论我ComboBox在设计器视图中将表单放在表单上,​​还是使用 nil 或TFrame作为其所有者在代码中动态创建它,都会发生这种情况。我目前使用包含的OnDestroy事件TForm来调用包含的清理过程TFrame

有没有更好的方法不需要TFrame容器的显式过程调用?理想情况下,动态添加到的对象应该在哪里ComboBox被释放?

4

4 回答 4

8

你说当 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;
于 2013-02-24T15:36:55.367 回答
8

您可以像这样使用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.

另一种选择是为整个应用程序创建一个 Base 祖先TAppBaseForm类和 a ,并将所有 Forms 派生为,并将所有 Frames 派生为。这样就可以通知所有它的孩子所有者表单在事件处理程序上被销毁。此时 ComboBox 项目仍然有效(如 Sertac Akyuz 的回答所述)。TAppBaseFrameTAppBaseFormTAppBaseFrameTAppBaseFormTAppBaseFrameTAppBaseForm.FormDestroy

于 2013-02-24T15:50:26.923 回答
4

您的问题并不是很有用,因为 - 一般而言 - 不鼓励数据(或您的情况下的对象)存储在 GUI 控件中。另请参阅 David 关于如何更改设计的评论。

使这个问题变得有趣的是,组合框直接作为表单的子项与作为表单的另一个子项(在这种情况下为您的框架)的子项之间的区别。显然,组合框项目在调用该框架的析构函数之前被销毁。探索的明显替代方案是:覆盖Frame.BeforeDestruction、覆盖Frame.DestroyWindowHandle、覆盖Frame.DestroyWnd或捕获WM_DESTROY覆盖Frame.WndProc,但在项目已经消失之前,它们都不会被调用。

接下来要尝试的是对组合框重复此操作。事实证明,当WM_DESTROY到达组合框时,项目仍然存在。但是,请注意仅在控件真正被销毁时才捕获该消息,因为 VCL 可能会频繁地重新创建组合框。使用插入类 for 来实现它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此外,我还通过在包含表单处理程序中将框架的属性设置为 nil找到了另一种选择OnDestroy

procedure TForm2.FormDestroy(Sender: TObject);
begin
  Frame1.Parent := nil;
end;

在这种情况下,您可以安全地销毁存储在框架析构函数内的组合框中的对象。但是这个解决方案比你现在的解决方案更糟糕,因为它不是描述性的。然后就好Frame1.FreeComboObjects很多了。

于 2013-02-24T13:29:40.960 回答
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;
于 2019-10-09T06:47:43.373 回答