When you write a program, there is no significant difference between a dialog box and another secondary form, aside from the border, the border icons, and similar user-interface elements you can customize.
What users associate with a dialog box is the concept of a modal window—a window that takes the focus and must be closed before the user can move back to the main window. This is true for message boxes and usually for dialog boxes, as well. However, you can also have nonmodal—or modeless—dialog boxes.
So, if you think dialog boxes are just modal forms, you are on the right track, but your description is not precise. In Delphi (as in Windows), you can have modeless dialog boxes and modal forms. You must consider two different elements: The form's border and its user interface determine whether it looks like a dialog box; the use of two different methods (Show and ShowModal) to display the secondary form determines its behavior (modeless or modal).
To add a second form to an application, you click the New Form button on the Delphi toolbar or use the File ® New ® Form menu command. As an alternative, you can select File ® New ® Other, move to the Forms or Dialogs page, and choose one of the available form templates or form wizards.
If you have two forms in a project, you can use the View Form or View Unit button on the Delphi toolbar to navigate through them at design time. You can also choose which form is the main one and which forms should be automatically created at startup using the Forms page of the Project Options dialog box. This information is reflected in the source code of the project file.
Secondary forms are automatically created in the project source-code file depending on the status of the Auto Create Forms check box on the Designer page of the Environment Options dialog box. Although automatic creation is the simplest and most reliable approach for novice developers and quick-and-dirty projects, I suggest that you disable this check box for any serious development. When your application contains hundreds of forms, they shouldn't all be created at application startup. Create instances of secondary forms when and where you need them, and free them when you're done.
Once you have prepared the secondary form, you can set its Visible property to True, and both forms will show up as the program starts. In general, the secondary forms of an application are left "invisible" and are then displayed by calling the Show method (or setting the Visible property at run time). If you use the Show function, the second form will be displayed as modeless, so you can move back to the first form while the second is still visible. To close the second form, you might use its system menu or click a button or menu item that calls the Close method. As you've just seen, the default close action (see the OnClose event) for a secondary form is simply to hide it, so the secondary form is not destroyed when it is closed. It is kept in memory (again, not always the best approach) and is available if you want to show it again.
Unless you create all the forms when the program starts, you'll need to check whether a form exists and create it if necessary. The simplest case occurs when you want to create multiple copies of the same form at run time. In the MultiWin/QMultiWin example, I've done this by writing the following code:
with TForm3.Create (Application) do Show;
Every time you click the button, a new copy of the form is created. Notice that I don't use the Form3 global variable, because it doesn't make much sense to assign this variable a new value every time you create a new form object. The important thing, however, is not to refer to the global Form3 object in the code of the form or in other portions of the application. The Form3 variable will invariably be a pointer to nil. My suggestion, in such a case, is to remove it from the unit to avoid any confusion.
In the code of a form that can have multiple instances, you should never explicitly refer to the form by using the global variable Delphi sets up for it. For example, suppose that in the code for TForm3 you refer to Form3.Caption. If you create a second object of the same type (the class TForm3), the expression Form3.Caption will refer to the caption of the form object referenced by the Form3 variable, which might not be the current object executing the code. To avoid this problem, refer to the Caption property in the form's method to indicate the caption of the current form object, and use the Self keyword when you need a specific reference to the object of the current form. To avoid any problem when creating multiple copies of a form, I suggest removing the global form object from the interface portion of the unit declaring the form. This global variable is required only for the automatic form creation.
When you create multiple copies of a form dynamically, remember to destroy each form object as is it closed, by handling the corresponding event:
procedure TForm3.FormClose(Sender: TObject; var Action: TCloseAction); begin Action := caFree; end;
Failing to do so will result in a lot of memory consumption, because all the forms you create (both the windows and the Delphi objects) will be kept in memory and hidden from view.
Now let's focus on the dynamic creation of a form, in a program that accounts for only one copy of the form at a time. Creating a modal form is quite simple, because the dialog box can be destroyed when it is closed, with code like this:
var Modal: TForm4; begin Modal := TForm4.Create (Application); try Modal.ShowModal; finally Modal.Free; end;
Because the ShowModal call can raise an exception, you should write it in a try block followed by a finally block to make sure the object will be de-allocated. Usually this block also includes code that initializes the dialog box before displaying it and code that extracts the values set by the user before destroying the form. The final values are read-only if the result of the ShowModal function is mrOK, as you'll see in the next example.
The situation is a little more complex when you want to display only one copy of a modeless form. You have to create the form, if it is not already available, and then show it:
if not Assigned (Form2) then Form2 := TForm2.Create (Application); Form2.Show;
With this code, the form is created the first time it is required and then is kept in memory, visible on the screen or hidden from view. To avoid using up memory and system resources unnecessarily, you'll want to destroy the secondary form when it is closed. You can do that by writing a handler for the OnClose event:
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction); begin Action := caFree; // important: set pointer to nil! Form2 := nil; end;
Notice that after you destroy the form, the global Form2 variable is set to nil, which contradicts the rule set earlier for forms with multiple instances, but as this is a single-instance we are in the exact opposite case. Without this code, closing the form would destroy its object, but the Form2 variable would still refer to the original memory location. At this point, if you try to show the form once more with the btnSingleClick method shown earlier, the if not Assigned() test will succeed, because it checks whether the Form2 variable is nil. The code fails to create a new object, and the Show method (invoked on a nonexistent object) will result in a system memory error.
As an experiment, you can generate this error by removing the last line of the previous listing. As you have seen, the solution is to set the Form2 object to nil when the object is destroyed, so that properly written code will "see" that a new form must be created before using it. Again, experimenting with the MultiWin/QMultiWin example can prove useful to test various conditions. (I haven't shown any screens from this example because the forms it displays are totally empty, except for the main form, which has three buttons.)
Setting the form variable to nil makes sense—and works—if there is to be only one instance of the form present at any given instant. If you want to create multiple copies of a form, you'll have to use other techniques to keep track of them. Also keep in mind that in this case you cannot use the FreeAndNil procedure, because you cannot call Free on Form2—you cannot destroy the form before its event handlers have finished executing.