我有一个包含TFrame的表单。TFrame包含一个动态填充的ComboBox。每个ComboBox条目都有一个关联的对象。在调用TFrame的重写析构函数时,ComboBox中的项已经被清除,而没有释放其关联的对象。无论是在设计器视图中将ComboBox拖放到窗体上,还是在代码中以nil或TFrame作为其所有者动态创建它,都会发生这种情况。我目前使用包含TForm的OnDestroy事件来调用包含的TFrame的清理过程。
有没有一种更好的方法不需要TFrame容器显式的过程调用?理想情况下应该在哪里释放动态添加到ComboBox的对象?
发布于 2013-02-24 23:36:55
您可以说,当调用TFrame的析构函数时,ComboBox的项已经被清除。事实并非如此,ComboBox项永远不会被清除。当物品被ComboBox销毁时,它们的计数只有0。
当您退出应用程序时,VCL会销毁包含frame和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-24 23:50:27
您可以像这样使用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就可以通过TAppBaseForm.FormDestroy事件处理程序通知它的所有子TAppBaseFrame所有者窗体已被销毁。此时,ComboBox项仍然有效(正如Sertac Akyuz的answer所描述的那样)。
发布于 2013-02-24 21:29:41
你的问题并不是很有用,因为一般来说,不鼓励在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就好多了。
https://stackoverflow.com/questions/15043297
复制相似问题