Where to free dynamically allocated TFrame component objects?

I have a form containing a TFrame . TFrame contains a ComboBox that is dynamically populated. Each ComboBox entry has an associated object. By the time the overridden destructor for TFrame , the items in the ComboBox already cleared without releasing related objects. This happens if I omit the ComboBox in the form in the constructor view or dynamically create it in code with zero or TFrame as its owner. I am currently using the OnDestroy event for the containing TForm to call the cleanup procedure of the contained TFrame .

Is there a better way that would not need to explicitly call a procedure with a TFrame container? Where ideally objects added dynamically to a ComboBox should be freed?

+6
source share
4 answers

You say that when the destructor for TFrame is called, the ComboBox elements are already cleared. This is not the case, ComboBox items are never cleared. When items are destroyed by a ComboBox, they only have a score of 0.

When you exit the application, and VCL destroys the form containing the frame and ComboBox, the native ComboBox component is also destroyed by the OS, because it is placed in a destructible window. When you later access the elements in order to be able to free objects in the frame destructor, the VCL must recreate its own ComboBox control with the element value 0.

The solution I propose is easy. Do not leave frame deallocation in the framework; instead, destroy your frame in the OnDestroy event of your form. This would be before the base window of the form is destroyed, therefore, you can access your objects.

unit of form

 procedure TMyForm.FormDestroy(Sender: TObject); begin MyFrame.Free; end; 

frame unit

 destructor TMyFrame.Destroy; var i: Integer; begin for i := 0 to ComboBox1.Items.Count - 1 do ComboBox1.Items.Objects[i].Free; inherited; end; 
+8
source

You can use the TFrame WM_DESTROY handler as follows:

 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. 

Another option is to create the base ancestor class TAppBaseForm and TAppBaseFrame for the entire application and get all your forms as TAppBaseForm and all frames as TAppBaseFrame . This way, TAppBaseForm can notify all TAppBaseFrame children that the owner form has been destroyed in the TAppBaseForm.FormDestroy event TAppBaseForm.FormDestroy . At this point, the ComboBox elements remain valid (as described by Sertac Akyuz answer ).

+6
source

Your question is not very useful, because - generally speaking - it is not recommended to store data (or objects in your case) in a graphical user interface control. See also David's comment on how to change the design.

Which makes the question respond to queries, although the difference between the combo box is a child of the form and is a descendant of another child of the form (in this case, your frame). Apparently, the elements with the list are destroyed before calling the destructor of this frame. Obvious search alternatives are: overriding Frame.BeforeDestruction , overriding Frame.DestroyWindowHandle , overriding Frame.DestroyWnd or intercepting WM_DESTROY in an overridden Frame.WndProc , but none of them are called before the elements have already disappeared.

The next thing to try is to repeat this for the combo box. It turns out when WM_DESTROY comes into the combo box that the items are still there. However, be careful to catch this message when the control is actually destroyed, because the VCL can often recreate the combo box. Implement it using the intermediate class for TComboBox as follows:

 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. 

Now, to answer your question: "Is this the best way?"

Yes, this is because it provides confidence in the destruction of an object at the frame level. In other words: you do not have to remember this for each case separately.

And no, this is not so, because this solution requires the objects in the combo box to be freed in any circumstances that limit the use of an unnecessary additional border.

So is this answer helpful? Well, if that is stopping you from using your current approach, then it is.


In addition, I also found another alternative by setting the frame Parent property to nil in the OnDestroy handler:

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

In this case, you can safely destroy the objects stored in the combo box inside the frame destructor. But this solution is even worse than your current one, because it is not descriptive. Then Frame1.FreeComboObjects much better.

+3
source

releasing Combobox.Items.Objects in the destructor too late. therefore, according to previous answers, it is better and safer to do so:

  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; 
0
source

All Articles