我认为唯一的方法是为此添加标志,但这是最好的方法吗?
当表单被销毁并且我检查 if(Assigned(form2)),结果是true?为什么?
如何处理这个问题?
您可以使用Form1.Showing来判断一个窗体是否已经关闭。
仅仅关闭一个窗体并不能释放它,除非您在OnClose事件中设置。默认值是caHide
。
哇,一段过去的回忆 :)
Assigned()函数的工作方式是对指针进行空值检查。如果你销毁了form2,仍然会有一个内存地址指向form2。
我已经很久没有用Delphi了,但是从记忆中来看,当form2被销毁时,你需要手动将form2变量设置为nil。如果你有一个中央位置(例如,一个窗体代理?)用于创建和销毁窗体,那么这应该很容易实现。
Form1.Free
或者Form1.Destroy
,Delphi会销毁对象但不会将对象引用设置为nil。因此,请使用FreeAndNil
。
如需更多信息,请查看Andreas Rejbrand在此链接中的回答。
procedure TMyForm.FormDestroy(Sender: TObject);
begin
MyForm:=nil;
end;
因此指针变为nil,我可以使用Assigned
来检查它或将其与nil
进行比较。
作为一个提示,在某些特殊情况下正确的做法是创建一个计时器来对变量进行nil赋值。
我会解释一下(有点复杂),如果你在自己的代码中创建了一个表单MyForm:=TMyForm.Create
,并且你有一个MyFrom.Close
,那么很容易,只需添加一个MyForm:=nil
或更好的是MyForm.FreeAndNil
...但有时引用不在任何地方。
例如:你在一个过程中创建了许多相同表单的副本(或只有一个),让表单保持打开状态并结束该过程,现在对打开的表单的引用已经不存在了,因此你无法以正常的方式分配nil或进行freeandnil等操作。
对于这种情况,诀窍是使用一个计时器(只有一毫秒)来完成它,该计时器需要引用,因此你必须像Self一样将所有内容存储在全局变量中,所有这些都可以在关闭事件上完成。
做免费(没有任何引用时)的最简单方法是在主窗体上创建一个TObjectList,这样它将保存所有需要释放的表单引用,并定义一个计时器(一毫秒),该计时器将遍历该列表并执行freeandnil;然后在onlcose中将Self
添加到该列表中并启用该计时器。
现在另一部分,您有一个在启动时自动创建的普通表单,但您需要将其设置为nil并在自己的代码中重新创建它。
该情况具有指向该表单的全局变量,因此您只需要释放和nil它,但不要(我大声说)在自己的表单代码内的任何部分上执行,您必须在表单代码之外执行(我大声说)。
有时,当用户关闭它且未以模态显示时,您需要释放该表单,这种情况很复杂,但同样的技巧有效,在onclose事件中,您启用一个计时器(通常在主窗体之外),该计时器将释放和nil。该计时器间隔可以设置为仅一毫秒,直到表单完全关闭才会运行它(请记住不要使用Application.ProcessMessages,这通常是一个非常糟糕的想法)。
如果你在自己的表单中将Self设置为nil、free或其他操作,可能会破坏应用程序内存(这样做是完全不安全的,更不用说它会占用RAM)。
唯一释放一个未显示为模态且由用户关闭的表单(并将其引用设置为nil)的方法是,在表单完全关闭后编写触发器来执行此操作。
我知道可以将操作设置为进行释放,但除此之外没有其他安全的方法来将其设置为nil。
必须说明:如果在主表单上使用计时器,请在Onclose事件上对所有计时器运行Enabled:=False
...否则可能会出现奇怪的事情(不总是,但有时会出现关于销毁应用程序和在计时器上运行代码的竞争条件),当然,如果某个计时器已启用,则正确地执行终止或中止等操作。
你的问题是要做的复杂事情之一...释放并将一个由用户操作而非代码关闭的表单设置为nil。
对于其余的部分:想象一下应用程序同时打开了许多表单,并且所有表单都可以与用户同时交互(任何一个都不是模态的),而您有代码从其他表单引用其中一些表单... 您需要知道用户是否关闭了任何表单,以避免从代码中访问该表单。这并不容易实现,除非您使用计时器。
如果您有一个“中央”表单(例如MDI应用程序),则可以将该计时器放在主MDI表单上,因此任何关闭的子表单都可以被释放和置空,技巧再次是在该主表单上设置计时器。
只有当您确定可以释放和置空所有不可见表单时,才可以在主表单上设置计时器,遍历所有表单,如果Visible为false,则调用FreeAndNil,我认为这种方式容易出错,因为如果您在未来添加一个不必被释放但可以保持隐藏的表单... 这段代码将无效。
始终记住,如果用户关闭了表单,必须释放和清空它,否则代码无法检测并处理,也不会启动任何事件(在表单完全关闭后),在表单完全关闭之前,您甚至不能试图释放或清空它的引用。如果主板有多个插槽,应用程序使用线程等,则更容易出现奇怪的问题。
因此,对于使用线程的应用程序(以及不使用线程的应用程序),我使用另一种方法,该方法效果很好,不需要计时器,但需要在每个ThatForm.*
之前进行双重检查,技巧是在公共部分定义一个表单布尔变量,例如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比较的地方),只需调用一个“全局”函数,传递引用(无论它是否为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 ...
。
就是这样。
这种方法比定时器更好,因为表单会尽快释放(使用的 RAM 更少),而使用 PleaseFreeAndNilMe
技巧时,只有在调用 IsNilTheForm
时才会释放表单(如果你没有在其他地方释放它)。
IsNilTheForm
如此复杂,是因为它考虑了所有状态(对于多插槽主板和线程应用程序),并让代码在任何其他地方释放 / 置空它。
当然,该函数必须在主线程中和原子排除中调用。
释放一个表单并将其指针置为空不是一件简单的事情,特别是当用户随时可以关闭它时(因为表单外部没有触发任何代码)。
最大的问题是:当用户关闭一个表单时,没有办法触发一个在该表单之外且在表单结束所有操作之后被触发的事件处理程序。
现在想象一下,程序员已经在整个应用程序中放置了大量的Application.ProcessMessages;,包括在那个窗体上等等...并且没有考虑到竞争条件...尝试在用户要求关闭它后释放和nil这样一个窗体...这是一场噩梦,但可以通过TForm的黑客版本来解决,该版本具有告诉窗体尚未被释放但希望被释放的变量。...= class(Forms.TForm)
,这样它就会有额外的变量。因此,调用IsNilTheForm
将作为与nil进行比较。还有一件事情可以做(但我总是尽量避免):拥有多个指向完全相同的窗体的变量(而不是它们的副本),这很容易出现很多错误,你释放其中一个变量之后就需要将所有变量都置为nil,等等...我展示的代码也与此兼容。
我知道代码很复杂...但释放和置nil一个窗体比我的代码更复杂。