2007. január 22., hétfő

Free a parent form when its child gets closed or freed


Problem/Question/Abstract:

I am using a TPageControl and show some forms on its pages. So, whenever I want to show a form I create a new page on the TPageControl for that form and then displat the form in that page. Now I want free that page when the user closes the form sitting on it. I tried using the form's OnClose or OnDestroy events to free the parent tabsheet of the form but I get an access violation.

Answer:

Solve 1:

It is difficult enough to destroy a control from an event handler of that control, trying to destroy its parent adds even more problems to that. The best way to handle this is to leave the destruction of the tabsheet to a neutral 3rd party, in this case the form holding the pagecontrol. In the embedded forms OnClose you post (via postmessage) a custom message to the form holding the pagecontrol and then hide the embedded form. The host form then destroys the tabsheet and that also destroys the embedded form. Posting the message delays the action long enough to allow any code in the embedded form to complete safely. Example:

{Unit for the embedded form}

unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs,
    StdCtrls;

const
  UM_KILLCONTROL = WM_USER + 666;
type
  TUMKillControl = record
    msg: Cardinal;
    control: TControl;
    unused: LPARAM;
    result: LRESULT;
  end;
type
  TForm2 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
  public
  end;

implementation

{$R *.dfm}

procedure TForm2.Button1Click(Sender: TObject);
begin
  close
end;

procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  action := caHide;
  PostMessage(GetParentForm(self).Handle, UM_KILLCONTROL, Integer(parent), 0);
end;

end.

{Unit for the host form}

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ComCtrls, StdCtrls, unit2;

type
  TForm1 = class(TForm)
    StatusBar: TStatusBar;
    Button1: TButton;
    PageControl1: TPageControl;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    procedure UMKillControl(var msg: TUMKillControl); message UM_KILLCONTROL;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
var
  tab: TTabSheet;
begin
  tab := TTabSheet.Create(self);
  tab.PageControl := pagecontrol1;
  with TForm2.create(self) do
  begin
    borderstyle := bsNone;
    parent := tab;
    tab.caption := caption;
    align := alclient;
    show;
  end;
end;

procedure TForm1.UMKillControl(var msg: TUMKillControl);
begin
  msg.control.Free;
end;

end.


Solve 2:

As long as the child form is not "owned" by the tabsheet on which it is parented (being owned by the form which owns the PageControl is OK), you can do this:

{ ... }
type
  TfNastyChild = class(TForm)
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
    fKillParent: TWinControl;
  public
    { Public declarations }
    destructor Destroy; override;
  end;

implementation

{$R *.dfm}

procedure TfNastyChild.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if (Parent is TTabsheet) and (Owner <> Parent) then
  begin
    Hide;
    fKillParent := Parent;
    Parent := nil;
  end;
  action := caFree;
end;

destructor TfNastyChild.Destroy;
begin
  if assigned(fKillParent) and not (csDestroying in fKillParent.ComponentState) then
    fKillParent.Free;
  inherited;
end;

Nincsenek megjegyzések:

Megjegyzés küldése