delphi 如何检查表格是否已关闭?

cu6pst1q  于 2023-03-08  发布在  其他
关注(0)|答案(5)|浏览(114)

我看到的唯一方法是为此添加flag,但这是最好的方法吗?
当表格被销毁时,我检查(Assigned(form2))结果是否为真?为什么?
有什么办法做到这一点?

0kjbasz6

0kjbasz61#

可以使用Form1.Showing查看窗体是否已关闭。
仅关闭窗体并不能释放它,除非在OnClose事件中设置Action := caFree。默认值为caHide

y4ekin9u

y4ekin9u2#

哇,来自过去的爆炸:)
Assigned()的工作原理是,它基本上对指针进行nil检查。如果你销毁了form2,仍然会有一个form2指向的内存地址。
我已经很长时间没有做过 Delphi 了,但是在内存中,你需要在销毁form2变量时手动将其设置为nil。如果你有一个中心位置(例如表单代理?),可以在那里创建和销毁表单,这应该很容易。

iqih9akk

iqih9akk3#

如果你使用Form1.FreeForm1.Destroy, Delphi 将销毁对象,但不会将对象引用设置为nil。
有关详细信息,请查看this link中的Andreas Rejbrand答案

3bygqnnd

3bygqnnd4#

在关闭应用程序时遇到了同样的问题。在这种情况下,所有表单都在后台销毁,但指针没有设置为空。以下代码帮助了我:

procedure TMyForm.FormDestroy(Sender: TObject);
begin
  MyForm:=nil;
end;

指针变为空,我可以用Assigned检查它,或者与nil比较。

kuarbcqp

kuarbcqp5#

作为一个提示,在一些特殊情况下,正确的方法是创建一个计时器,为变量赋值nil。
我会解释它(不知何故,它是复杂的),如果你在自己的代码MyForm:=TMyForm.Create中创建你的表单,你有一个MyFrom.Close,这是非常容易的,只是添加一个MyForm:=nil或更好的MyForm.FreeAndNil ...但有时参考不是anyware。
示例:你在一个过程中,在一个循环中创建了相同窗体的许多副本(或只有一个),让窗体打开并结束该过程,现在对打开窗体的引用无处可寻,所以你不能以正常的方式赋值nil或执行freeandnil等操作。
在这种情况下,技巧是使用一个计时器(只有一毫秒)来完成它,该计时器需要引用,所以你必须存储在一个全局变量上,比如对Self的引用,所有这些都可以在on close事件上完成。
最简单的释放方法(当没有引用时)是在主表单上创建一个TObjectList,这样它将保存所有需要释放的表单引用,并定义一个计时器(1毫秒),它将遍历该列表执行freeandnil;然后在onlcose上将Self添加到该列表并启用该计时器。
现在是另一部分,您有一个在启动时自动创建的标准表单,但您需要将其设置为nil,然后在自己的代码中重新创建它。
这种情况下有一个全局指向该窗体,所以你只需要释放和零它,但不是(我说大声)在自己的窗体代码内的任何部分,你必须这样做(我说如果大声)的窗体代码。
有时候你需要释放表单,当用户关闭它时,它不会以模态显示,这种情况很复杂,但是同样的技巧是有效的,在onclose事件上你启用一个计时器(即超出该窗体,通常在主窗体上),并且计时器将空闲并归零。计时器间隔可以设置为仅一毫秒,它将不会运行,直到窗体完全关闭(请记住不要使用Application.ProcessMessages,这通常是一个非常糟糕主意)。
如果你把Self设置为nil、free或者在自己的表单中的其他值,你可能会破坏你的应用程序内存(这样做是完全不安全的,更不用说它会吃掉内存)。
释放窗体(并使其引用为空)的唯一方法是编写一个触发器,使其在窗体完全关闭后执行此操作。窗体不显示为模态,关闭它的是用户。
我知道如何设置动作来完成自由,但要将其设置为零,没有其他安全的方法。
必须说明:如果你在主窗体上使用计时器,在Onclose事件中对所有计时器运行Enabled:=False ...否则可能会发生奇怪的事情(不是总是,但有时...关于破坏应用程序和在计时器上运行代码的竞态条件),当然,如果某个计时器被启用,正确地终止它或中止它,等等。
您的问题是要做的复杂事情之一......释放和清空一个不是由代码而是由用户操作关闭的表单。
对于所有其他:想象一下,如果应用程序同时打开了许多表单,并且所有表单都可以同时与用户交互(任何人都是模态的),并且您有代码从其他表单引用其中的一些表单...您需要知道用户是否关闭了任何表单,以避免从代码访问该表单。除非使用计时器,否则这一点很重要。
如果你有一个"中央"窗体(如MDI应用程序),你可以把计时器放在主MDI窗体上,这样任何关闭的子窗体都可以被释放和清空,技巧还是在主窗体上放置计时器。
只有当你确信你可以释放和清空所有不可见的表单时,你才可以在主表单上设置一个计时器,如果Visible为false,那么调用FreeAndNil,我认为这种方式容易出错,因为如果你在将来添加一个表单,它不能被释放,但可以保持隐藏...这段代码将是无效的。
请始终记住,如果用户是关闭必须释放的表单的唯一用户,并且表单为空,则代码无法检测并执行操作,也不会启动任何事件(在窗体完全关闭之后)和在窗体完全关闭之前,您甚至不能尝试释放它或使其引用为空,可能会发生奇怪的事情(如果主板有多个插座,更容易出现这种情况,如果你的应用程序使用线程等,也更容易出现这种情况)。
因此,对于线程应用程序(也不线程)我使用另一种方法,工作很好,不需要计时器,但需要在每个ThatForm.*之前进行双重检查,技巧是在窗体的公共部分定义一个窗体bolean公共变量,如PleaseFreeAndNilMe,然后在onclose(作为最后一行)将其设置为True,并在OnCreate将其设置为False。
这样,您就可以知道该窗体是已关闭还是仅隐藏(要隐藏窗体,不要调用close,只需调用hide)。
因此,代码将如下所示(您可以将其用作一个变形器,而不是将表单定义为TForm,将其定义为TMyform,或者更好的是,使用类似type TForm=class(Forms.TForm)的方法,而不是TMyForm=class(TForm),将该变量添加到所有表单中):

TMyForm=class(TForm)
...
public
      PleaseFreeAndNilMe:=Boolean;
...
procedure TMyForm.FormCreate(Sender: TObject);
begin
     PleaseFreeAndNilMe:=False;
     ...
end;
procedure TMyForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
     ...
     PleaseFreeAndNilMe:=True;
end;

如果你喜欢黑客版本:

TForm=class(Froms.TForm)
public
      PleaseFreeAndNilMe:=Boolean;
end;
procedure TForm.FormCreate(Sender:TObject);
begin
     inherited Create(Sender);
     PleaseFreeAndNilMe:=False;
end;
procedure TForm.FormClose(Sender:TObject;var Action:TCloseAction);
begin
     PleaseFreeAndNilMe:=True;
     inherited FormClose(Sender,Action);
end;

但是正如我所说的,在访问任何成员之前(或者只是在你做nil比较的地方),只需要调用一个'global'函数来传递引用(不管它是否为nil),代码如下:

function IsNilTheForm(var TheForm: TMyForm);
begin
     if nil=TheForm
     then begin // The form was freed and nil
               IsNilTheForm:=True; // Return value
          end
     else begin // The form refence is not nil, but we do not know is it has been freed or not
               try
                  if TheForm.PleaseFreeAndNilMe
                  then begin // The form is not freed but wants to
                            try
                               TheForm.Free;
                            except
                            end;
                            try
                               TheForm:=Nil;
                            except
                            end;
                            IsNilTheForm:=True; // Return value
                       end
                  else begin // The form is not nil, not freed and do not want to be freed
                            IsNilTheForm:=False; // Return value
                       end; 
               except // The form was freed but not set to nil
                    TheForm:=Nil; // Set it to nil since it had beed freed
                    IsNilTheForm:=True; // Return value
               end;
          end;         
end;

所以你在做if nil=MyForm then ...的地方,现在你可以做if (IsNilTheForm(MyForm)) then ...
就是这样。
计时器解决方案更好,因为窗体会尽可能快地释放(使用的内存更少),而PleaseFreeAndNilMe技巧是在调用IsNilTheForm之前不释放窗体(如果您不在其他任何地方释放它)。
IsNilTheForm是如此复杂,因为它考虑了所有状态(对于多插槽主板和线程应用程序),并让代码在其他任何地方自由/零它。
当然,该函数必须在主线程上和原子排除中调用。
释放一个窗体并使其指针为空不是一件小事,大多数情况下用户可以随时关闭它(因为窗体中没有代码被触发)。
最大的问题是:当用户关闭窗体时,没有办法让事件处理程序在窗体外触发,并且在窗体结束它正在做的所有事情之后。
现在Imaigne的编码器已经放了很多Application.ProcessMessages;在应用程序上的每一个地方,也在那个表单上,等等......并且没有注意到竞态条件......在用户要求关闭这样的表单后,尝试释放和清空它......这是一个噩梦,但是可以通过TForm的黑客版本来解决,该版本有一个变量,告诉用户表单没有被释放,但是需要它。
现在假设你使用了一个被破解的TForm,并且想要一个普通的TForm,只需将它定义为...= class(Forms.TForm),这样它就有了额外的变量。,所以调用IsNilTheForm将相当于与nil进行比较。
希望这能帮助VCL程序员修复这样的问题,比如当一个对象被销毁,释放,niled,隐藏等时引发一个事件......在那个对象的代码之外,比如在主窗体上,等等。这会让生活更轻松......或者只是修复它......关闭和释放意味着将所有指向它的引用都设置为Nil。
还有一件事可以做(但我总是尽量避免):有多个变量指向完全相同的形式(而不是它的副本),这是很容易发生很多错误,你释放一个,并需要零所有他们,等等...我所显示的代码也与之兼容。
我知道代码是复杂的...但自由和零的形式比我的代码更复杂。

相关问题