2004. június 8., kedd

Managing a Single Instance of a Non-Modal Form


Problem/Question/Abstract:

How to manage the application's non-modal forms properly - to make sure that only one instance of a form is present at any given time, like it is done automatically for modal forms?

Answer:

When you have a modal form, you create, display and destroy it usually in a single uninterrupted sequence of code instructions, therefore it is easy to ensure that your form variable is equal to NIL after the form is expired, so you easily eliminate the risk of creating the same form twice or, even worse, attempting to access non-existing form when the only form's remains you actually have is the form variable with non-NIL value in it.

The problem with non-modal forms is that you usually create and display them in one place, then do whatever you want with your application (and not only that form), and order them to destroy themselves when they are no longer needed at any time - you cannot say beforehand where and when that will happen.
It is natural to close and free such a non-modal form from itself, but where to put clean-up instructions then? Without them, the application cannot know whether the form is alive - it relies merely on the form variable value (is it equal to NIL?) to determine if the form is still around. It is possible to introduce some application-level flags to keep track of the forms presence, but it is not an elegant solution.

Fortunately, as almost always with Delphi, there is a clear way to solve the problem.

Suppose you have frmNonModal as a form variable of TfrmNonModal class.

The procedure is simple:

   1. Add the following OnDestroy event handler to the form:

procedure TfrmNonModal.FormDestroy(Sender: TObject);
begin
  frmNonModal := nil;
  inherited;
end; {TfrmNonModal.FormDestroy}

Note that you cannot substitute frmNonModal with Self, though it seems to be the same from the first glance. The difference is that Self points to the object itself (and we don't want to nullify it prematurely), and frmNonModal is just an external (to the object) variable, which points to the object. By setting this variable to NIL we do not affect any internal functionality of the object, but rather just disconnect its link to the outer world. In our case this is exactly what we need, as we plan to check this variable when creating the form, as shown below.  

   2. Add the following OnClose event handler to the form:

procedure TfrmNonModal.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  inherited;
  Action := caFree;
end; {TfrmNonModal.FormClose}

This is needed to instruct our form to free itself when we close it. Note that by default Action is caHide, i.e. your form is just being closed (hidden), but not destroyed after calling TForm.Close.
  
   3. Bring your form to life as follows, for example in the menu item's OnClick event handler on your main form (of TfrmMain class):

procedure TfrmMain.itmNonModalFormClick(Sender: TObject);
begin
  if Assigned(frmNonModal) then
  begin
    {if the form already exists, just bring it to the front}
    frmNonModal.Show;
    frmNonModal.WindowState := wsNormal;
    frmNonModal.BringToFront;
  end
  else
  begin
    {create the form if it does not exist}
    frmNonModal := TfrmNonModal.Create(Application);
    try
      {display as non-modal form}
      frmNonModal.Show;
    except
      {clean up the form and form variable if unsuccessful}
      FreeAndNil(frmNonModal);
    end; {try}
  end; {if}
end; {TfrmMain.itmNonModalFormClick}

That's it. Now your application always knows whether your non-modal form is present (created), and manages to allow not more than a single instance of such form at a time.

Nincsenek megjegyzések:

Megjegyzés küldése