What's the best way to hide forms until you need them? This seems like a simple question but there are really two answers. The first answer is simple: Auto-create all forms and use Show/Hide when needed. The second answer is a little more complicated, but generally gives better memory and performance results: Manually create forms and use Show/Destroy when needed. This article will explain some of the tradeoffs and help you choose the answer that's right for you, depending on your application.
Figure A: Here's the Forms tab of the Project Options dialog box.
Notice that, by default, all forms show up in the Auto-create list and the first one in the list is selected as the main form. All forms will be constructed in the order shown in that list. The Project Options dialog box gives you the ability to move forms up or down in the list or to select a different form as the main form. However, the one selected as the main form is always moved to the top of the list and constructed first.
This approach by Borland made our programming jobs much easier. Since all forms are auto-created, they only need to be made visible by calling Show(). When a form is closed, it's really just hidden by the system and can easily be re-shown later.
The only exception to this is the main form. The form specified as the main form will be displayed at application startup, regardless of the value of its Visible property. When the main form is closed, instead of simply being hidden, it queues the application to terminate and all forms are destroyed. That's why the Project Options dialog box specifically names which form is the main form. The main form is constructed first, is displayed regardless of its Visible property, and, when closed, terminates the application.
Not having to reconstruct forms each time you need them is a great timesaver. All you need to do is call Show() or Hide() to display or hide the forms. Figure B shows an example main form (Form1) and a supporting form (Form2).
Figure B: The main and supporting forms are examples for this technique.
Listing A shows the code for handling button events that show and hide the supporting form. The best part is that since the form is only hidden, when it's displayed again, it will be in the same position on the screen as before, and will retain all the entries and settings the user saw before.
Listing A: Simple Show/Hide code
//-----------------------------------------
void __fastcall TForm1::ShowBtnClick(
TObject *Sender)
{
// Show the supporting form.
Form2->Show();
}
//-----------------------------------------
void __fastcall TForm1::HideBtnClick(
TObject *Sender)
{
// Hide the supporting form.
Form2->Hide();
}
//-----------------------------------------
Of course, with simplicity comes inefficiency. Since all forms are constructed
at startup, they all take up memory. Even forms that you may only use once or
perhaps never use (depending on the selections made by the user) are
constructed and chew up RAM. For small programs, this isn't much to worry
about. However, for programs that have many and/or large form classes, the
construction of these forms may severely cut into your available memory.
Another drawback is that your program may take longer to load during startup, since processing time must be used by the system to allocate memory and construct all of the forms. Again, for small applications, it's nothing to worry about. For larger projects, your user may end up staring at the hourglass cursor for 15-30 seconds while the program loads.
Listing B: Dynamic code in main form
//---------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
// Flag the form pointer as empty.
Form2 = 0;
}
//---------------------------------------------------
void __fastcall TForm1::ShowBtnClick(
TObject *Sender)
{
// Construct the supporting form object.
// if that hasn't been done yet.
if ( !Form2 ) Form2 = new TForm2( this );
// Show the supporting form.
Form2->Show();
}
//---------------------------------------------------
void __fastcall TForm1::HideBtnClick(
TObject *Sender)
{
// Check to make sure the supporting form
// object even exists.
if ( Form2 )
{
// Release memory and clean up.
delete Form2;
// Flag the form pointer as empty.
Form2 = 0;
}
}
//---------------------------------------------------
void __fastcall TForm1::FormDestroy(
TObject *Sender)
{
// Make sure the supporting form is
// cleaned up.
if ( Form2 ) delete Form2;
}
//---------------------------------------------------
When
the Show button is selected, the supporting form object must be created (using
the new menu choice) and then shown. When the Hide button is selected, the form
must be destroyed. To the user, this approach appears exactly the same as the
Auto-create method. The supporting form appears and disappears when the buttons
are selected.
Since we're handling the form object's construction and destruction ourselves, there are a couple of additional things we need to add to our code. In the main form's constructor, notice that we give an initial value of 0 to the supporting form's pointer. This serves as a flag, so that later we can tell whether an object has been created and assigned to this pointer.
Some code also had to be added to the form's destruction event handler. This is done so that the supporting form will be properly destroyed, in case the main form is closed by the user while the supporting form (Form2) is still open.
The main advantage of this approach is that memory isn't allocated until the user wants to see each supporting form, and is reclaimed when the user closes the form. Another advantage is that, for programs with many supporting forms, the application will load and start up faster, since fewer objects need to be constructed at that time.
Things are not all rosy, though. This approach requires more coding and concentration on your part--as the developer. In addition, since the form object is re-created each time, its previous position and entries are lost unless you write even more code to save and restore them each time.