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

Posted

技术标签:

【中文标题】如何在 Delphi 的 TFrame 上模拟 OnDestroy 事件?【英文标题】:How to simulate an OnDestroy event on a TFrame in Delphi? 【发布时间】:2011-04-28 02:47:08 【问题描述】:

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


我在我的框架中添加了 constructordestructor,认为这就是 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 销毁窗体的窗口句柄。这会递归地破坏所有子窗口 - 包括我框架上的那些。

所以当我的框架的destructor 运行时,我尝试访问不再处于有效状态的控件。


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

另见

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

【问题讨论】:

那些框架没有 OnCreate 和 OnDestroy 是设计使然。现在找不到帖子/文章/信息(所以是评论而不是答案),但我记得很久以前看到 borland 和/或 teamb 成员的回复(由 QC 编号见证)说明了这一点。 IIRC 的原因显然不是触发这些事件的好时机,可以确定为实例化的简单性和表单的破坏被流和可视表单/框架继承混淆了。 为什么不使用 OnCreate(由 borland 研发成员提供,由 TeamB 的 Jeff Overcash 转达):codenewsfast.com/isapi/isapi.dll/…。相同的推理可能适用于 OnDestroy 事件。因此,在制定您的解决方案时,我会注意这些注意事项。 【参考方案1】:

您需要添加一个 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)。

【讨论】:

这确实是一个优雅的答案。我有时会忘记我们的应用程序存在于 Windows 平台上,而 Windows 平台已经拥有丰富的系统来完成任务。【参考方案2】:

听起来更像OnClose 而不是OnDestroy

无论如何,我只是从一个基本祖先继承了我的所有框架和窗体,然后窗体的 onclose 调用组件层次结构中的所有框架。

【讨论】:

如果有人想要使用我的独立框架并且愿意重新构建他们的整个应用程序,那么这将是可行的。也不是OnClose,因为表单可以再次打开。它确实创建/销毁,但框架兼容。【参考方案3】:

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

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

【讨论】:

【参考方案4】:

另一种选择是覆盖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;

【讨论】:

我尝试覆盖BeforeDestruction。这还不够快 - 控制已经给出并回复了他们的WM_DESTROY 消息。 您可以检查它的方法是在constructor 期间用项目填充组合框,并在destructor 期间观察组合框已经为空(并且存储在ComboBox1.Items.Objects 中的任何对象都已泄漏) 如果你直接释放框架,它的所有组件仍然可以访问,你遇到的问题是你释放父窗体时。在这种情况下,您应该按照 Craig Peterson 的建议使用 WM_DESTROY,或者只是从父表单的 FormDestroy 处理程序手动通知框架

以上是关于如何在 Delphi 的 TFrame 上模拟 OnDestroy 事件?的主要内容,如果未能解决你的问题,请参考以下文章

在对象检查器上显示 TFrame 后代的附加属性

Delphi如何检测表单上任何地方的点击事件,包括其他组件

TFrame bug

Delphi,框架与表格。多文档界面是啥?

Delphi中Frame和Form有何区别

delphi中WebBrowser控件怎么模拟点击网页框架中的按钮button?