如何在Delphi中关闭非模态窗体

3

这个问题在这里已经讨论过,但没有这么详细。

当我试图关闭一个非模态子窗体时,我遇到了麻烦。我让它通知父窗体,但是我得到了抽象错误和其他异常。我做错了什么?父窗体必须释放非模态窗体,还是永远不再尝试通过该变量访问它?

主窗体:

NonModal := NonModalTForm.Create(Self);
NonModal.Callback := Callback;
NonModal.Show;

Procedure TForm.Callback; // called by non-modal form when closing 
begin
   FreeAndNil(NonModal);  // or should this just be NonModal := nil so I don't try to access a dangling pointer?
end;

在NonModal.pas文件中

procedure NonModalTForm.FormClose;
begin
  Callback; // calls parent
end;

7
你的代码就像砍断你所坐的树枝一样,这在编程中意味着自食其果。 - Loren Pechtel
5个回答

12

在FormClose事件之外的其他地方关闭表单时,需要调用close函数。在FormClose事件中,只需将Action设置为以下之一:

  • caFree - 完全释放表单
  • caMinimize - 最小化表单
  • caHide - 隐藏表单
  • caNone - 忽略关闭请求

例如:

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;

虽然多年来我一直很喜欢这种方法,但我发现在使用VCL样式时,例如在System.@UStrAddRefSystem.@UStrLAsg中,这会生成随机的'EAccessViolation'错误。 - Anse
刚学到了使用 caFree 时出现访问冲突问题只会发生在模态窗体中。非模态窗体和 VCL 样式都没问题。 - Anse
@Anse 为什么你要在模态窗体中使用 caFree?模态窗体是阻塞的,所以在窗体的 showmodal 后,你需要从调用者中释放它。 - skamradt

8

VCL已经有一种机制可以在其他组件被释放时通知组件。您可以按照以下方式使用它:

type
  TfrmParent = class(TForm)
    btnShowChild: TButton;
    procedure btnShowChildClick(Sender: TObject);
  private
    FChild: TfrmChild;
  public
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  end;


procedure TfrmParent.btnShowChildClick(Sender: TObject);
begin
  // Check status of child
  if FChild = nil then
  begin
    // Child does not exist, create it
    FChild:= TfrmChild.Create(Application);
    FChild.Show;

    // Ask Child to notify us when it is destroyed
    FChild.FreeNotification(Self);
  end
  else
  begin
    // Child already exists
    FChild.Show;
  end;
end;

procedure TfrmParent.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;

  if (AComponent = FChild) and (Operation = opRemove) then
  begin
    // FChild is about to be freed, so set reference to Child to nil
    FChild:= nil;
  end;
end;

创建子窗体后,使用创建的窗体的FreeNotification方法注册自己,以便在子窗体销毁时接收通知。
为了响应通知,请重写Notification方法。在其中,您可以找出被销毁的组件,并将其与对子窗体的记忆引用进行比较。当您收到通知时,只需将对子窗体的引用设置为nil即可。
在子TfrmChild本身中,除了skamradt编写的内容之外,您不需要做任何其他事情:只需在OnClose事件中将参数Actionb设置为caFree即可。

4
当将 Self 传递给 TfrmChild.Create() 而不是 Application 时,甚至不需要调用 FreeNotification() - VCL 中的所有权管理会处理通知调用。 - mghie
1
这里唯一有效的决定。只有它将闭合形式设置为“nil”。干得好。我想知道为什么投票较少。 - Val Marinov
1
我刚刚将这个标记为答案,13年后。也许是某种记录吧?不管延迟多久,我想感谢你提供的清晰答案,这些年来帮了我很多! - undefined

6

如果您想稍后显示窗口,请使用隐藏功能。

如果您想关闭窗口,请使用关闭功能。(关闭主窗口将关闭应用程序)关闭的确切操作取决于表单参数。

查看关闭的源代码:

procedure TCustomForm.Close;
var
  CloseAction: TCloseAction;
begin
  if fsModal in FFormState then
    ModalResult := mrCancel
  else
    if CloseQuery then begin
      if FormStyle = fsMDIChild then
        if biMinimize in BorderIcons then
          CloseAction := caMinimize 
        else
          CloseAction := caNone
      else
        CloseAction := caHide;
      DoClose(CloseAction);
      if CloseAction <> caNone then
        if Application.MainForm = Self then 
          Application.Terminate
        else if CloseAction = caHide then 
          Hide
        else if CloseAction = caMinimize then 
          WindowState := wsMinimized
        else 
          Release;
    end;
end;

但是要小心使用免费的方法。因为在窗口队列中可能会有一些消息残留,这可能会导致崩溃。最好使用“Release”来清理窗口。因为它会在释放之前等待所有消息被处理完。


4
你正在做一件不应该做的事情。
在非模态表单的 onClose 事件中,你调用了一些代码来粗暴地释放它,而此时它仍然在事件处理程序执行中,因此你最终得到的自身对象已经无效了。
这正是为什么要在 Form 上使用 Release 而不是 Free 的典型案例。ReleaseFree
正如 Gamecat 所指出的,只需简单地调用 Close...
VCL 的美妙之处通常就在于它就是这么简单。

正如Gamecat指出的那样,只需要简单地调用Close... 您是说在回调过程中调用Close,该回调过程本身是从非模态的FormClose本身调用的?那不会触发第二个OnClose事件吗?还是您是在说VCL足够智能,不会第二次调用非模态表单的FormClose。 - RobertFrank
2
你的“回调”函数与这个讨论无关,汤姆。正如你所指出的,你正在调用它在表单的“OnClose”事件处理程序中。这意味着当回调函数被调用时,你的表单已经正在关闭,因此你整个问题都是无意义的:你不需要学习如何关闭表单,因为你已经完成了,即使你不知道你是如何做到的。 - Rob Kennedy
没有回调程序,我该如何避免父指针指向非模态表单时出现内存泄漏和/或悬空指针?我必须释放NonModal指向的内存...并将NonModal设置为nil,以便我知道它没有在使用。我唯一能想到的解决方案是再加一层逻辑:非模态表单在关闭时调用父级的回调函数,将标志“非模态表单已关闭”设置为True。稍后(当自身正在关闭或想要重用非模态表单时),检查此标志并释放内存。很抱歉我一直在这个问题上纠结,但我真的在尝试找出解决方法,而不需要使用回调程序。 - RobertFrank
您不需要释放NonModal,因为它是通过将Self(即MainForm)作为Owner创建的。Owner会在其自身销毁时释放它。在此之前,除非有些代码明确释放并将其设置为Nilled,否则仍可重复使用。如果需要自己释放它,您始终可以检查它是否已关闭。 - Francesca
谢谢Francois和其他人。我的问题现在得到了解答。我的困惑源于我没有理解NonModal.Close会让该窗体重新可用。再次感谢大家的帮助! - RobertFrank
@Tom:这并不一定,它取决于你控制的关闭操作。请参考skamradt的答案(+1)。另外,如果你的问题得到了解答,应该接受帮助你最多的答案。 - mghie

-2

不要使用 callback

只需调用 FreeAndNil(Self); 以释放为窗体创建的所有内存资源。

记得释放由您的实现代码创建的对象。窗体设计器创建的对象已经被 Delphi 很好地清理了。


我认为这个答案并不补充已经给出的其他答案。其次,正如这个答案(特别是它上面的这个评论)所述,在事件处理程序中释放窗体确实是不可取的。抱歉,打-1分。 - NGLN

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接