How to simulate an OnDestroy event on a TFrame in Delphi?
How can i simulate an OnDestroy
event for a TFrame
in Delphi?
i nievely 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 TCustomFor开发者_运维问答m.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. 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?
See also
- Simulating OnCreate and OnDestroy for a Frame?
- How to Implement the OnCreate event for a Delphi TFrame object
- Embargadero QC#1767: TFrame misses OnCreate, OnDestroy, OnShow
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).
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.
(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
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;
精彩评论