2013-02-23 101 views
6

我有一個包含TFrame的表單。 TFrame包含動態填充的ComboBox。每個ComboBox條目都有一個關聯的對象。在調用TFrame的重寫析構函數時,ComboBox中的項目已被清除,但未釋放其關聯的對象。無論我在設計器視圖中刪除表單上的ComboBox,還是以代碼動態創建代碼(無論是否爲TFrame作爲其所有者),都會發生這種情況。我目前使用包含TFormOnDestroy事件來調用包含的TFrame的清理過程。何處釋放動態分配的TFrame組件的對象?

有沒有更好的方法,不需要TFrame的容器明確的過程調用?在理想情況下應該將動態添加到ComboBox的對象釋放出來嗎?

+6

我的建議是改變你的設計。沒有擁有這些對象的組合框。讓框架擁有你喜歡的任何容器,例如'TObjectList '。然後你可以在你的框架的析構函數中銷燬那個容器。 – 2013-02-23 18:07:59

回答

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好得多。

+1

您是否認爲在ComponentState中測試csDestroying可以取代FRecreating? – 2013-08-24 13:30:15

+0

@Sertac是的,它更乾淨,更具描述性。改變了它。 – NGLN 2013-08-24 16:24:18

7

你說當TFrame的析構函數被調用時,ComboBox的Items已經被清除了。情況並非如此,ComboBox項目永遠不會被清除。當項目被組合框銷燬時,它們的計數僅爲0.

當您退出應用程序並且VCL銷燬包含框架和組合框的表單時,本機組合框控件也被操作系統銷燬因爲它被放置在一個被破壞的窗口中。當您以後訪問這些項目以便能夠在框架析構函數中釋放對象時,VCL必須重新創建項目計數爲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; 
+0

+1。一個很好的解決方案。 – kobik 2013-02-24 16:13:10

+0

@kobik - 謝謝。我也喜歡你的,因爲它與破壞形式是分開的。 – 2013-02-24 17:05:47

6

你可以利用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. 

另一種選擇是CR請爲整個應用程序分配一個基本祖先TAppBaseForm類和TAppBaseFrame,並將所有表單作爲TAppBaseForm和所有的幀作爲TAppBaseFrame。 這種方式TAppBaseForm可以通知所有它的子女TAppBaseFrame所有者窗體在TAppBaseForm.FormDestroy事件處理程序被銷燬。此時組合框項目仍然有效(如Sertac Akyuz的answer所述)。