2013-02-23 22 views
6

Tôi có một biểu mẫu có chứa TFrame. TFrame chứa một số ComboBox được điền động. Mỗi mục nhập ComboBox có một đối tượng liên quan. Vào thời điểm các destructor ghi đè cho TFrame được gọi là, các mục trong ComboBox đã được xóa mà không giải phóng các đối tượng liên quan của họ. Điều này xảy ra cho dù tôi thả ComboBox vào biểu mẫu trong chế độ xem của nhà thiết kế hoặc tự động tạo mã trong mã với số không hoặc số TFrame làm chủ sở hữu của nó. Tôi hiện đang sử dụng sự kiện OnDestroy có chứa số TForm để gọi thủ tục dọn dẹp có chứa TFrame.Nơi để tự do phân bổ các đối tượng thành phần của TFrame?

Có cách nào tốt hơn mà không cần gọi thủ tục rõ ràng theo vùng chứa của TFrame không? Trường hợp lý tưởng các đối tượng được thêm động vào số ComboBox sẽ được giải phóng?

+6

Lời khuyên của tôi là thay đổi thiết kế của bạn. Không có hộp combo sở hữu các đối tượng này. Hãy để khung sở hữu chúng trong bất kỳ vùng chứa nào bạn thích, ví dụ: 'TObjectList '. Sau đó, bạn có thể phá hủy thùng chứa đó trong trình phá hủy khung của bạn. –

Trả lời

3

Câu hỏi của bạn không thực sự hữu ích, bởi vì - nói chung - không được khuyến khích để lưu trữ dữ liệu (hoặc các đối tượng trong trường hợp của bạn) trong điều khiển GUI. Xem thêm bình luận của David về cách thay đổi thiết kế của bạn.

Điều khiến câu hỏi đặt câu trả lời là sự khác biệt giữa hộp kết hợp là con của biểu mẫu trực tiếp và là con của một đứa trẻ khác trong biểu mẫu (khung của bạn trong trường hợp này). Rõ ràng, các mục combo box là bị phá hủy trước khi destructor của frame đó được gọi. Các lựa chọn thay thế rõ ràng để khám phá sau đó: ghi đè Frame.BeforeDestruction, ghi đè Frame.DestroyWindowHandle, ghi đè Frame.DestroyWnd hoặc bắt WM_DESTROY trong số bị ghi đè Frame.WndProc, nhưng không ai trong số chúng được gọi trước khi các mục đã biến mất.

Điều tiếp theo cần thử là lặp lại điều này cho hộp tổ hợp. Nó chỉ ra rằng khi WM_DESTROY đến tại hộp combo rằng các mục vẫn còn đó. Tuy nhiên, hãy cẩn thận khi bắt gặp thông điệp đó một cách vô cùng khi kiểm soát thực sự bị phá hủy, bởi vì VCL có thể tái tạo một hộp kết hợp thường xuyên. Thực hiện nó bằng một lớp xen cho TComboBox, như sau:

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. 

Bây giờ, để trả lời câu hỏi của bạn: "Đây có phải là một cách tốt hơn?"

Có, bởi vì nó đảm bảo sự hủy diệt của đối tượng ở cấp độ khung hình. Nói cách khác: bạn không cần phải nhớ để giải quyết vấn đề này cho mọi trường hợp một cách riêng biệt.

Và không có nó là không, bởi vì giải pháp này đòi hỏi rằng các đối tượng trong hộp kết hợp được phép được giải phóng trong bất kỳ hoàn cảnh nào hạn chế sử dụng đến một ranh giới phụ không cần thiết.

Vì vậy, câu trả lời này có hữu ích không? Vâng, nếu nó ngăn cản bạn sử dụng cách tiếp cận hiện tại của bạn, thì đúng vậy.


Bên cạnh đó, tôi cũng nhận thấy sự thay thế khác bằng cách thiết lập thuộc tính Parent của khung để nil theo hình thức chứa OnDestroy handler:

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

Trong trường hợp này, bạn có thể yên tâm tiêu diệt các đối tượng được lưu trữ trong các combo trong khung hủy của khung. Nhưng giải pháp này thậm chí còn tồi tệ hơn so với hiện tại của bạn, bởi vì nó không phải là mô tả. Sau đó, Frame1.FreeComboObjects là tốt hơn nhiều.

+1

Bạn có nghĩ rằng thử nghiệm cho csDestroying trong ComponentState có thể được thay thế thay cho FRecreating? –

+0

@Sertac Có, và nó sạch hơn và mô tả hơn. Đã thay đổi nó. – NGLN

7

Bạn nói rằng khi trình phá hủy cho TFrame được gọi, Các mục của ComboBox đã bị xóa. Đó không phải là trường hợp, các mục ComboBox không bao giờ bị xóa. Khi các mục bị hủy bởi ComboBox, chúng sẽ chỉ đếm được 0.

Khi bạn thoát ứng dụng và VCL phá hủy biểu mẫu chứa khung và ComboBox, điều khiển ComboBox gốc cũng bị phá hủy bởi hệ điều hành vì nó được đặt trong một cửa sổ bị phá hủy. Khi bạn truy cập vào các mục sau để có thể giải phóng đối tượng của bạn trong khung hủy, VCL phải tạo lại một điều khiển ComboBox gốc, có tổng số mục là 0.

Giải pháp tôi đề xuất là dễ dàng. Thay vào đó, đừng để khung của bạn giải phóng khung, phá hủy khung của bạn trong sự kiện OnDestroy của biểu mẫu của bạn. Điều đó sẽ xảy ra trước khi cửa sổ bên dưới của biểu mẫu bị hủy, do đó bạn sẽ có thể truy cập các đối tượng của mình.

đơn vị hình thức

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

đơn vị khung

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. Một giải pháp rất tốt đẹp. – kobik

+0

@kobik - Cảm ơn. Tôi cũng thích bạn vì nó bị tách rời khỏi hình thức hủy diệt. –

6

Bạn có thể sử dụng các 's TFrameWM_DESTROY xử lý như thế này:

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. 

lựa chọn khác là cr ăn một tổ tiên cơ sở TAppBaseForm lớp học và một TAppBaseFrame cho toàn bộ ứng dụng, và lấy được tất cả các hình thức của bạn như TAppBaseForm và tất cả các khung như TAppBaseFrame. Bằng cách này, TAppBaseForm có thể thông báo cho tất cả con của nó là TAppBaseFrame rằng Biểu mẫu chủ sở hữu bị hủy trên TAppBaseForm.FormDestroy trình xử lý sự kiện. Tại thời điểm đó các mục ComboBox vẫn còn hợp lệ (như được mô tả bởi Sertac Akyuz's answer).

Các vấn đề liên quan