John White
John White

Reputation: 403

How do I check if form is Closed?

The only way I see is to add flag for this, but is this the best way?

When the form is destroyed and I check if(Assigned(form2)) the result is true? Why?

What is the way to do this?

Upvotes: 5

Views: 11185

Answers (5)

claudio
claudio

Reputation: 1

Just as a tip, the correct way to do it in some special cases is to create a timer that do the nil assign to the variable.

I will explain it (somehow it is complex), if you create your form within your own code MyForm:=TMyForm.Create and you have a MyFrom.Close it is very easy, just add a MyForm:=nil or also better MyForm.FreeAndNil... but sometimes the reference is not anyware.

Sample: You create inside a procedure, on a loop a lot of copies of the same form (or just one), let the form open and end that procedure, now the reference to the opened form is nowhere, so you can not assign nil or do a freeandnil, etc., in a normal way.

For that cases, the trick is to use a timer (of just one milisecond) that do it, that timer needs the reference, so you must store on a global like the reference to Self, all that can be done on the on close event.

The easiest way to do the free (when no reference anywhere) is to create a TObjectList on the main form, so it will hold all form references that needs to be free, and define a timer (one milisecond) that will go through that list doing the freeandnil; then on the onlcose you add Self to that list and enable that timer.

Now the other part, you have a normal form that is auto created on start, but you need to set it to nil and re-create it on your own code.

That case has a global that point to that form, so you only need to free and nil it, but NOT (i say it loud) on any part inside the own form code, you must do it OUT (i say if loud) of the form code.

Some times you will need to free the form, when user close it, and it is not shown in modal, this case is complex, but again the same trick is valid, on the onclose event you enable a timer (that is out of that form, normally on main form) adn that timer will free and nil. That timer interval can be set as just one milisecond, it will not be run until form has totally closed (please have in mind not using Application.ProcessMessages, that is normally a really bad idea).

If you set Self to nil, free or whatever inside the own form, you can corrupt your application memory (doing that is totally unsafe, not to mention it can eat ram).

The only way to free a form (and nil its reference), a form that is not shown as modal and is the user who close it, is to program a trigger that do that after the form is totally closed.

I know about setting action to do the free, but to set it to nil there is no other safe way.

Must say: If you use timers on your main form, run a Enabled:=False on all of them on the Onclose event... otherwise weird things may occur (not allways, but sometimes... race conditions about destroying application and running code on that timers), and of couse if some one was enabled act correctly to terminate it correctly or abort it, etc.

Your question is one of the complex things to do... free and nil a form that is closed not by code, but by user action.

For all the rest: Think like if the application has at the same time a lot of forms opened and all can interact with the user at the same time (anyone is modal), and you have code that references some of them from the others... you need to know f user has closed any form to avoid accesing that form from code. This is not trivial to be done unless you use timers.

If you have a 'central' form (like an MDI application) you can put that timer on the main MDI form, so any child form that is closed can be freed and nil, the trick is again a timer on that main form.

Only and only if you are sure you can free and nil all non visible forms, you can have a timer on the main form that goes through all forms and if Visible is false, then call FreeAndNil, i consider this way prone to errors, since if you add on a future a form that must not be freed but can stay hidden... this code will not be valid.

Allways remember that if is the user the onw who closes the form that must be freed and nil, there is no way on code to detect and act, no event is launched (after the form is totally closed) and before the form is totally closed you must not even try to free it or nil its reference, weird things can occur (more prone to that if motherboard has more than one socket, also more if your app uses threads, etc).

So, for threaded apps (and also not threaded) i use another method that works great and do not need timers, but need double checking prior to each ThatForm.*, the trick is to define a Form bolean public variable like as PleaseFreeAndNilMe on the public section on the form, then on the onclose (as last line) set it to True and on the OnCreate set it as False.

That way you will know if that form had been closed or only hidden (to hide a form never call close, just call hide).

So coded will look like (you can use this as a warper, instead of defining forms as TForm define them as TMyform, or also better, use a hack like type TForm=class(Forms.TForm) instead of TMyForm=class(TForm) just to have that variable added to all forms):

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;

If you prefer hacked version:

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;

But as i said, prior to access any member (or just where you do the nil compare) just call a 'global' function passign the reference (no matter if it was nil or not), coded as:

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;

So where you do if nil=MyForm then ... you can now do if (IsNilTheForm(MyForm)) then ....

That is it.

It is better the timer solution, since form is freed as soon as possible (less ram used), with the PleaseFreeAndNilMe trick the form is not freed until IsNilTheForm is called (if you do not free it any where else).

That IsNilTheForm is so complex because it is considering all states (for a multi socket motherboard and threaded apps) and letting the code free / nil it anywhere else.

Of course, that function must be called on main thread and in atomic exclusion.

Free a Form and Nil its pointer is not a trivial thing, most when user can close it at any time (since no code out of the form is fired).

The big problem is: When a user closes a form there is no way to have a event handler that is triggered out of that form and after the form ends all things it is doing.

Imaigne now that the coder has put a lot of Application.ProcessMessages; every where on the app, also on that form, etc... and has not taken the care for race conditions... try to free and nil such a form after the user asks it to be closed... this is a nightmare, but can be solved with the hacked version of TForm that has a variable that tells that the form has not been freed but wants it.

Now imagine you use hacked TForm and want a normal TForm, just define it as ...= class(Forms.TForm), that way it will now have that extra variable., so calling IsNilTheForm will act as comparing to nil.

Hope this helps VCL coders to FIX such things, like raising an event when an object is destroyed, freed, niled, hide, etc... out of the code of that object, like on main form, etc. That would make live easier... or just fix it... Close and Free implies set to Nil all refences that point to it.

There is another thing that can be done (but i try to allways avoid it): Have multiple variables that point to the exact same form (not to copies ot it), that is prone to a lot of errors, you free one and need to nil all of them, etc... The code i show is also compatible with that.

I know the code is comples... but Free and Nil a form is more complex than my code.

Upvotes: -2

Alexander Zaripov
Alexander Zaripov

Reputation: 36

Faced with the same issue when doing some routine on closing application. In this case all forms are destroyed behind the stage but pointers are not set to nil. This code helphs me:

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

So pointer becomes nil and I can check it with Assigned or compare to nil.

Upvotes: 1

Mikael Eriksson
Mikael Eriksson

Reputation: 139000

You can use Form1.Showing to see if a form is closed or not.

Just closing a form does not free it unless you set Action := caFree in OnClose event. Default is caHide.

Upvotes: 9

Bharat
Bharat

Reputation: 6866

If you use Form1.Free or Form1.Destroy, Delphi will destroy the object but wont set the object reference to nil. So instead use FreeAndNil.

For more information, check Andreas Rejbrand answer in this link

Upvotes: 4

Raymond Barlow
Raymond Barlow

Reputation: 489

Wow, a blast from the past :)

The way that Assigned() works, is that it basically does nil check on the pointer. If you destroy form2, there will still be a memory address that form2 points to.

I has been a very long time since I've done any Delphi, but from memory, you need to manually set the form2 var to nil when it is destroyed. If you have a central place (eg. a form broker?) where you create & destroy forms, this should be quite easy.

Upvotes: 4

Related Questions