How to simulate an OnDestroy event on a TFrame in Delphi?
Asked Answered
R

4

7

How can I simulate an OnDestroy event for a TFrame in Delphi?


I naively added a constructor and destructor to my frame, thinking that is what TForm does:

TframeEditCustomer = class(TFrame)
...
public
   constructor Create(AOwner: TComponent); override;
   destructor Destroy; override;
   ...
end;

constructor TframeEditCustomer.Create(AOwner: TComponent)
begin
    inherited Create(AOwner);
    
    //allocate stuff
end;

destructor TframeEditCustomer.Destroy;
begin
   //cleanup stuff

   inherited Destroy;
end;

The problem with this is that by the time my destructor runs, controls on the frame have been destroyed and are no longer valid.

The reason for this is in the containing form's destructor, which it uses to fire an OnDestroy event:

destructor TCustomForm.Destroy;
begin
   ...
   if OldCreateOrder then DoDestroy; //-->fires Form's OnDestroy event; while controls are still valid
   ...
   if HandleAllocated then DestroyWindowHandle; //-->destroys all controls on the form, and child frames
   ...
   inherited Destroy; //--> calls destructor of my frame
   ...
end;

The destructor of my frame object is being called when the form's destructor runs. The problem with this is that it's too late. The form calls DestroyWindowHandle, which asks Windows to destroy the form's window handle. This recursively destroys all child windows - including those on my frame.

So when my frame's destructor runs, I attempt to access controls that are no longer in a valid state.


How can I simulate an OnDestroy event for a TFrame in Delphi?

Remittance answered 20/10, 2010 at 15:4 Comment(2)
That frames have no OnCreate and OnDestroy is by design. Can't find the post/article/information right now (so a comment and not an answer), but I remember seeing responses from borland and/or teamb members a long time ago (as witnessed by the QC number) stating as much. IIRC the reason apparently was no good moment to fire these events could be identified as the simplicity of instantiation and destruction of forms is confounded by streaming and visual form/frame inheritance.Angloindian
Why no OnCreate (by borland R&D member, as conveyed by Jeff Overcash from TeamB): codenewsfast.com/isapi/isapi.dll/…. The same reasoning probably applies for the OnDestroy event. So I'd be mindful of these caveats when crafting your solution.Angloindian
H
11

You need to add a WM_DESTROY handler and check for csDestroying in the ComponentState so it's only caught when actually destroying, and not when recreating the handle.

type
  TCpFrame = class(TFrame)
  private
    FOnDestroy: TNotifyEvent;
    procedure WMDestroy(var Msg: TWMDestroy); message WM_DESTROY;
  published
    property OnDestroy: TNotifyEvent read FOnDestroy write FOnDestroy;
  end;

procedure TCpFrame.WMDestroy(var Msg: TWMDestroy);
begin
  if (csDestroying in ComponentState) and Assigned(FOnDestroy) then
    FOnDestroy(Self);
  inherited; 
end;

That will only work if the frame's window handle has actually been created. There isn't another good hook point, so if you want to ensure it's always called you'll need to set a flag in WMDestroy and fall back to calling it in the destructor if that isn't hit.

The window handles themselves are all cleared in WM_NCDESTROY, which is called after all of the descendant WM_DESTROY messages return, so the form and all of its childens' handles should still be valid at this point (ignoring any that were freed in the form's OnDestroy).

Hecht answered 20/10, 2010 at 16:37 Comment(1)
This really is the elegant answer. I sometimes tend to forget that our applications exist on the Windows platform, which already have a rich system to accomplish tasks.Remittance
F
1

Sounds more like OnClose than OnDestroy.

Anyway, I just inherited all my frames and forms from a base ancestor, and the form's onclose calls then all frames in the component hierarchy.

Fruit answered 20/10, 2010 at 15:21 Comment(1)
That works if someone who wants to use my self-contained frame is willing to re-architect their entire application. Also it's not so much an OnClose because the form can be opened again. It really it create/destroy, but frame compatible.Remittance
T
0

(It's just an idea but I haven't got the time right now to construct a proof of concept, but I'll share it none the less:)

If it's a problem with the Windows handle(s), you should check wether you're able to attach a Windows' event callback pointer that gets called when the frame's Windows handle ceases to exists. Perhaps with a function like RegisterWaitForSingleObject

Tribromoethanol answered 20/10, 2010 at 16:33 Comment(0)
M
0

Another option is to override AfterConstruction and BeforeDestruction

Something like this:

  TMyFrame = class(TFrame)
  private
    FOnCreate: TNotifyEvent;
    FOnDestroy: TNotifyEvent;
  protected
    procedure DoCreate; virtual;
    procedure DoDestroy; virtual;
  public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
    property OnCreate: TNotifyEvent read FOnCreate write FOnCreate;
    property OnDestroy: TNotifyEvent read FOnDestroy write FOnDestroy;
  end;

  implementation

  procedure TMyFrame.AfterConstruction;
  begin
    inherited;
    DoCreate;
  end;

  procedure TMyFrame.BeforeDestruction;
  begin
    inherited;
    DoDestroy;
  end;

  procedure TMyFrame.DoCreate;
  begin
    if Assigned(FOnCreate) then
      FOnCreate(Self);
  end;

  procedure TMyFrame.DoDestroy;
  begin
    if Assigned(FOnDestroy) then
      FOnDestroy(Self);
  end;
Moria answered 20/10, 2010 at 21:14 Comment(3)
i tried overriding BeforeDestruction. It's not soon enough - the controls have already been given, and responded to, their WM_DESTROY message.Remittance
The way you can check it is to fill a combobox with items during the constructor and observe during destructor that the combobox is already empty (and any objects stored in ComboBox1.Items.Objects have leaked)Remittance
If you free the frame directly all of it's components are still accessible, the problem you're having is when you free the parent form. In that case you should use WM_DESTROY as Craig Peterson suggested, or just notify frames manually from parent form's FormDestroy handlerMoria

© 2022 - 2024 — McMap. All rights reserved.