Any serious Windows application will have one or more forms. Most forms are used to retrieve information from the user (aside from simple forms like an About form). Your program must have some way of extracting information from the form and, in some cases, sending information to the form. This article will discuss various ways of transferring information from the main form to a secondary form, and vice versa.
PWForm->ShowModal(); if (PWForm->PWEdit->Text == "bubba") // password failed else // password succeededHere, we read the value of the password edit component directly through the password form's pointer. This is a one-way transfer of data because we're only reading the data on the form and aren't making any attempt to set data on the form. This situation is only slightly more complicated when you take control of creating the form yourself (in cases where the form isn't auto-created at application startup). Here's how the code might look:
TPWForm* form = new TPWForm(this); form->ShowModal(); if (form->PWEdit->Text == "bubba") // password failed else // password succeeded delete form;The only item of significance here is that you must remember to extract any data from the form before you delete the form's pointer. Once the form's pointer has been deleted, any attempts to access the pointer will result in an access violation or, in extreme cases, a complete system crash. By the way, I never let the VCL auto-create my forms in real-world applications. I always allocate the memory for the form just before I show the form and free that memory as soon as I'm done with the form.
Let's say, for example, that you had an application with an Options form. The Options form, naturally, would allow the user to specify options that control how the application behaves. For the sake of argument, let's say you had an application that dials a remote machine in order to transfer some data via a modem. The user options for this type of program might include the phone number to dial, the name of the TAPI device used to make the connection, and the number of times to retry dialing if the number is busy. These settings would be stored in the Registry, in an INI file, or in a user-defined configuration file. The options would be read from the storage medium to variables in the main form's class at application startup. When the application closes, the options would be saved to persistent storage for retrieval the next time the application runs. Transferring the data to and from the Options form will look something like this:
TOptionsForm* form = new
TOptionsForm(this);
form->PhoneNumber->Text = PhoneNumber;
form->TapiDevice->Text = TapiDevice;
form->DialRetries->Text = DialRetries;
if (form->ShowModal() == mrOk) {
PhoneNumber = form->PhoneNumber->Text;
TapiDevice = form->TapiDevice->Text;
DialRetries =
form->DialRetries->Text.ToInt();
}
delete form;
Here, we're only dealing with three controls on the form, and yet the code
already looks messy. If you had a large number of components on a form, the
code would be even more unwieldy. Plus, we aren't showing the code required to
save and load the data from persistent storage. That's a lot of code in the
main unit of your application that probably doesn't need to be there at all.
Now, let's look at an alternative to this method of data transfer.
In the context of this article, encapsulation means that you should, whenever possible, write your forms so that they're completely self-contained and don't need to have interaction with the main form at all. This is the ideal way to deal with forms in your applications. There are many types of forms, however, where this approach isn't practical because data needs to be transferred between the main form and the form.
class TAppOptions {
public:
String PhoneNumber;
String TapiDevice;
int DialRetries;
TAppOptions();
LoadFromRegistry();
SaveToRegistry();
};
As you can see, we've created data members for each option on the Options form.
We've also added methods to load from the Registry and save to the Registry.
Note that this class violates OOP principles by making all data members public,
but for simple examples, this is an acceptable practice.
An instance of this class will be created in the main form of the application.
Typically, this would be done at application startup in either the main form's
constructor or in its OnCreate event handler. At creation time, the previous
options could be read from persistent storage. For example:
void __fastcall
TMainForm::FormCreate(TObject *Sender)
{
Options = new TAppOptions;
Options->LoadFromRegistry();
}
The application's options are now loaded into the class represented by the
Options variable and are available for the main form's use. The Options
variable is declared as a member of the main form's class.
Naturally, you'll want to destroy the TAppOptions instance before the
application terminates. Either the main form's destructor or the OnDestroy
event handler are logical places for this code. Before you destroy the object,
however, you'll want to save the current options to persistent storage. Here's
how the code would look when placed in an OnDestroy event handler:
void __fastcall
TMainForm::FormDestroy(TObject *Sender)
{
Options->SaveToRegistry();
delete Options;
}
Using this technique, the application's options are always loaded at
application startup and saved again at application termination. Alternatively,
you could place the code to load the options from persistent storage in the
class constructor and the code to save the options in the class destructor.
class TOptionsForm : public TForm
{
__published: // IDE-managed Components
// component declarations here
private: // User declarations
TAppOptions* Options;
public: // User declarations
__fastcall TOptionsForm(
TComponent* Owner, TAppOptions*
options);
};
Notice that we've added a TAppOptions pointer to the private section of the
class. Notice, also, that the declaration for the constructor has been expanded
to allow us to pass a TAppOptions pointer from the main form.
Let's skip ahead just a bit and explain how the Options form will be used in
the main form. Using the modified TOptionsForm class requires that you create
an instance of the Options form at runtime. Since the Options form now has a
specialized constructor, you can't rely on the VCL's auto-creation feature for
forms. Here's how the code in the main form would look:
void __fastcall
TMainForm::Options1Click(TObject *Sender)
{
TOptionsForm* form =
new TOptionsForm(this, Options);
form->ShowModal();
delete form;
}
This code assumes that the Options variable had already been instantiated as
discussed in the previous section.
__fastcall TOptionsForm::TOptionsForm(
TComponent* Owner, TAppOptions* options)
: TForm(Owner)
{
Options = options;
PhoneNumberEdit->Text =
Options->PhoneNumber;
TapiDeviceEdit->Text =
Options->TapiDevice;
DialRetriesEdit->Text =
Options->DialRetries;
}
As you can see, we simply take each data member in the Options class and assign
its value to a corresponding component on the Options form. This part of the
operation is fairly simple.
If the form is closed with the OK button, on the other hand, we need to act to ensure that the new options are implemented. We simply provide an OnClick event handler for the OK button and copy data from the form's controls to the Options class instance. This is essentially the opposite of the code we wrote for the form's constructor. For example:
void __fastcall
TOptionsForm::OKBtnClick(TObject *Sender)
{
Options->PhoneNumber =
PhoneNumberEdit->Text;
Options->TapiDevice =
TapiDeviceEdit->Text;
Options->DialRetries =
DialRetriesEdit->Text.ToIntDef(0);
Close();
}
Since our local Options variable is simply a pointer to the instance created in
the main class, this code updates the main class' instance directly. When the
Options form closes, the main form's Options variable already contains the new
options. The main form doesn't have to change at all to account for the new
options. This is where encapsulating the application's options in the Options
form has great benefit. The Options form does all the work of updating the
options and the main form doesn't have to do any extra processing.
If there's a drawback to this mechanism, it's that you need to create a
transfer class for each of your application's forms. Still, this method is
preferred over those discussed earlier.
Listing A: MAINU.H
//---------------------------------------------
#ifndef MainUH
#define MainUH
//---------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <Menus.hpp>
#include "OptionsU.h"
//---------------------------------------------
class TMainForm : public TForm
{
__published: // IDE-managed Components
TMainMenu *MainMenu1;
TMenuItem *File1;
TMenuItem *Exit1;
TMenuItem *Tools1;
TMenuItem *Options1;
void __fastcall FormCreate(TObject *Sender);
void __fastcall FormDestroy(TObject *Sender);
void __fastcall Exit1Click(TObject *Sender);
void __fastcall
Options1Click(TObject *Sender);
private: // User declarations
TAppOptions* Options;
public: // User declarations
__fastcall TMainForm(TComponent* Owner);
};
//---------------------------------------------
extern PACKAGE TMainForm *MainForm;
//---------------------------------------------
#endif
Listing B: MAINU.CPP
//---------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "MainU.h"
//---------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMainForm *MainForm;
//---------------------------------------------
__fastcall
TMainForm::TMainForm(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------
void __fastcall
TMainForm::FormCreate(TObject *Sender)
{
// Create an instance of TAppOptions and
// load from the registry.
Options = new TAppOptions;
Options->LoadFromRegistry();
}
//---------------------------------------------
void __fastcall
TMainForm::FormDestroy(TObject *Sender)
{
// Save the options to the registry and
// then delete the TAppOptions object.
Options->SaveToRegistry();
delete Options;
}
//---------------------------------------------
void __fastcall
TMainForm::Exit1Click(TObject *Sender)
{
Close();
}
//---------------------------------------------
void __fastcall
TMainForm::Options1Click(TObject *Sender)
{
// Create an instance of the TOptionsForm
// class, passing the Options variable as
// a paramter. TOptionsForm does the rest.
TOptionsForm* form =
new TOptionsForm(this, Options);
form->ShowModal();
delete form;
}
//---------------------------------------------
Listing C: OPTIONSU.H
//---------------------------------------------
#ifndef OptionsUH
#define OptionsUH
//---------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <Registry.hpp>
//---------------------------------------------
// The registry key where we will save the data.
const String RegKey =
"Software\\ZDJournals\\DialogTransferEx";
// The TAppOptions class for transferring data.
class TAppOptions {
public:
String PhoneNumber;
String TapiDevice;
int DialRetries;
TAppOptions() {
PhoneNumber = "";
TapiDevice = "";
DialRetries = 0;
}
LoadFromRegistry() {
TRegistry* reg = new TRegistry;
reg->OpenKey(RegKey, true);
if (reg->ValueExists
("PhoneNumber")) {
PhoneNumber =
reg->ReadString
("PhoneNumber");
TapiDevice =
reg->ReadString
("TapiDevice");
DialRetries =
reg->ReadInteger
("DialRetries");
}
delete reg;
}
SaveToRegistry() {
TRegistry* reg = new TRegistry;
reg->OpenKey(RegKey, true);
reg->WriteString(
"PhoneNumber", PhoneNumber);
reg->WriteString(
"TapiDevice", TapiDevice);
reg->WriteInteger(
"DialRetries", DialRetries);
delete reg;
}
};
class TOptionsForm : public TForm
{
__published: // IDE-managed Components
TLabel *Label1;
TLabel *Label2;
TLabel *Label3;
TEdit *PhoneNumberEdit;
TEdit *TapiDeviceEdit;
TEdit *DialRetriesEdit;
TButton *OKBtn;
TButton *CancelBtn;
void __fastcall
CancelBtnClick(TObject *Sender);
void __fastcall OKBtnClick(TObject *Sender);
private: // User declarations
TAppOptions* Options;
public: // User declarations
__fastcall TOptionsForm(
TComponent* Owner, TAppOptions* options);
};
//---------------------------------------------
extern PACKAGE TOptionsForm *OptionsForm;
//---------------------------------------------
#endif
Listing D: OPTIONSU.CPP
//---------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "OptionsU.h"
//---------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TOptionsForm *OptionsForm;
//---------------------------------------------
__fastcall TOptionsForm::TOptionsForm(
TComponent* Owner, TAppOptions* options)
: TForm(Owner)
{
// Save the incoming TAppOptions pointer.
Options = options;
// Transfer data from the Option class
// to the individual controls on the form.
PhoneNumberEdit->Text =
Options->PhoneNumber;
TapiDeviceEdit->Text =
Options->TapiDevice;
DialRetriesEdit->Text =
Options->DialRetries;
}
//---------------------------------------------
void __fastcall
TOptionsForm::CancelBtnClick(TObject *Sender)
{
Close();
}
//---------------------------------------------
void __fastcall
TOptionsForm::OKBtnClick(TObject *Sender)
{
// Transfer data from the individual controls
// to the Options class.
Options->PhoneNumber =
PhoneNumberEdit->Text;
Options->TapiDevice =
TapiDeviceEdit->Text;
Options->DialRetries =
DialRetriesEdit->Text.ToIntDef(0);
Close();
}
//---------------------------------------------