如何在Delphi中模拟TFrame上的OnDestroy事件?

发布于 2024-09-28 07:28:24 字数 1276 浏览 11 评论 0原文

如何在 Delphi 中模拟 TFrameOnDestroy 事件?


我天真地将构造函数析构函数添加到我的框架中,认为这就是TForm所做的:

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;

问题是,到我的析构函数时运行时,框架上的控件已被破坏并且不再有效。

其原因在于包含表单的析构函数,它使用该析构函数来触发 OnDestroy 事件:

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;

当表单的析构函数运行时,将调用我的框架对象的析构函数。问题是为时已晚。表单调用DestroyWindowHandle,它要求Windows销毁表单的窗口句柄。这会递归地销毁所有子窗口 - 包括我框架上的子窗口。

因此,当我的框架的析构函数运行时,我尝试访问不再处于有效状态的控件。


如何在 Delphi 中模拟 TFrameOnDestroy 事件?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

审判长 2024-10-05 07:28:24

您需要添加 WM_DESTROY 处理程序并检查 ComponentState 中的 csDestroying,以便仅在实际销毁时捕获它,而不是在重新创建句柄时捕获它。

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;

仅当框架的窗口句柄已实际创建时,这才有效。没有其他好的挂钩点,因此如果您想确保它始终被调用,您需要在 WMDestroy 中设置一个标志,并在未命中时回退到在析构函数中调用它。

窗口句柄本身在 WM_NCDESTROY 中全部清除,该窗口句柄在所有后代 WM_DESTROY 消息返回后调用,因此此时窗体及其所有子句柄应该仍然有效(忽略在窗体 OnDestroy 中释放的任何句柄) 。

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).

嘿嘿嘿 2024-10-05 07:28:24

听起来更像是 OnClose 而不是 OnDestroy

无论如何,我只是从基础祖先继承了所有框架和表单,并且表单的 onclose 调用了组件层次结构中的所有框架。

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.

怎会甘心 2024-10-05 07:28:24

(这只是一个想法,但我现在没有时间构建概念证明,但我仍然会分享它:)

如果是 Windows 句柄问题,您应该检查是否“能够附加一个 Windows”事件回调指针,当框架的 Windows 句柄不再存在时,该指针将被调用。也许使用像 RegisterWaitForSingleObject 这样的函数

(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

驱逐舰岛风号 2024-10-05 07:28:24

另一种选择是覆盖 AfterConstructionBeforeDestruction

如下所示:

  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;

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;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文