2013-02-23 3 views
6

У меня есть форма, содержащая TFrame. TFrame содержит ComboBox, который динамически заполняется. Каждая запись ComboBox имеет связанный объект. К тому времени, когда вызывается переопределенный деструктор для TFrame, Элементы в ComboBox уже очищены, не освобождая связанные объекты. Это произойдет, если я опустил ComboBox на форму в дизайнерском представлении или динамически создавал ее в коде с нулем или TFrame в качестве своего владельца. В настоящее время я использую событие OnDestroy содержащего TForm, чтобы вызвать процедуру очистки содержащегося TFrame.Где можно разместить динамически распределенные объекты компонентов TFrame?

Есть ли лучший способ, который не требовал бы явного вызова процедуры контейнером TFrame? Где в идеале должны быть освобождены объекты, добавленные динамически к ComboBox?

+6

Мой совет - изменить свой дизайн. Не используйте поле со списком этих объектов. Пусть кадр принадлежит им в любом контейнере, который вам нравится, например. 'TObjectList '. Затем вы можете уничтожить этот контейнер в деструкторе вашего фрейма. –

ответ

3

Ваш вопрос не очень полезен, потому что - вообще говоря - не рекомендуется хранить данные (или объекты в вашем случае) в элементе управления графическим интерфейсом. См. Также комментарий Дэвида о том, как изменить дизайн.

В чем заключается вопрос, в котором возникает вопрос о том, как отвечать на вопрос, является ли разница между полем со списком, являющимся дочерним элементом формы, и быть ребенком другого ребенка формы (в этом случае ваша рамка). По-видимому, элементы со списком уничтожены до того, как вызывается деструктор этого фрейма. Очевидными альтернативами для изучения являются: переопределение Frame.BeforeDestruction, переопределение Frame.DestroyWindowHandle, переопределение Frame.DestroyWnd или перехват WM_DESTROY в переопределенном Frame.WndProc, но ни одна из них не вызывается до того, как элементы уже ушли.

Следующее, что нужно попробовать - это повторить это для поля со списком. Оказывается, когда 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. 

Теперь, чтобы ответить на ваш вопрос: «Является ли это лучше?»

Да, это потому, что оно обеспечивает уверенность в разрушении объекта на уровне кадра. Другими словами: вам не нужно , чтобы иметь дело с этим для каждого экземпляра отдельно.

И нет, это не так, поскольку это решение требует, чтобы объекты в поле со списком были освобождены в любых обстоятельствах, которые ограничивают использование ненужной дополнительной границей.

Итак, этот ответ полезен? Ну, если это мешает вам использовать ваш текущий подход, то это так.


Кроме того, я также нашел другую альтернативу, установив Parent свойства фрейма к нулю в вмещающей форме OnDestroy обработчика:

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

В этом случае, вы можете безопасно уничтожить объекты, хранящиеся в комбо в пределах деструктора кадра. Но это решение еще хуже, чем ваш текущий, потому что он не описательный. Тогда Frame1.FreeComboObjects намного лучше.

+1

Считаете ли вы, что тестирование для csDestroying в ComponentState можно заменить вместо FRecreating? –

+0

@Sertac Да, и это чище и более описательно. Изменено. – NGLN

7

Вы говорите, что , когда вызывается деструктор для TFrame, Элементы ComboBox уже очищены. Это не тот случай, элементы ComboBox никогда не очищаются. Когда элементы уничтожаются 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; 
+0

+1. Очень приятное решение. – kobik

+0

@kobik - Спасибо. Мне нравится ваша, потому что она отделена от формы уничтожения. –

6

Вы можете использовать TFrame «s 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. 

Другой вариант CR eate a base ancestor TAppBaseForm и TAppBaseFrame для всего приложения и получить все ваши формы как TAppBaseForm и все фреймы как TAppBaseFrame. Таким образом, TAppBaseForm мог бы уведомить всех своих детей TAppBaseFrame, что форма владельца уничтожена на TAppBaseForm.FormDestroy обработчик события. В этот момент элементы ComboBox остаются в силе (как описано Sertac Akyuz's answer).