October 1998

Sharing data and methods between forms

by Bill Whitney

Most of the C++Builder programs you write will contain a main form supported by one or more secondary forms. The way these forms communicate with each other and share information can not only add to the strength of your object-oriented design, but also improve the overall cohesiveness and efficiency of your application.

In this article, we'll focus on ways to open up communication between forms. We'll discuss some techniques you can use to centralize widely used objects, exchange data, and make calls to methods implemented in other forms within your application. Finally, we'll show you a short sample application that makes use of these techniques.

Hey, forms are objects too

All C++Builder forms are defined in a C++ class, meaning that they're objects as far as your application is concerned. Because they are objects, you have control over what attributes and methods they contain. This means that you can treat a C++Builder form as if it were any other C++ object instance, adding methods and attributes to suit your needs. Using the techniques we'll cover, you can increase your code's efficiency by centrally maintaining attributes and objects that will be used throughout your application. Next, let's look at where to store this information and some ways to get at it.

In touch with your main form

Every C++Builder application has a designated main form that's instantiated automatically when the program is executed. This form is an ideal place to store common information and services. You'll find it a useful repository for objects that you want to use throughout an application, but want to instantiate only once (because they're slow to construct or use a lot of memory). Before you can do anything useful with the main form, however, you need to learn how to access it from anywhere within your program. You'll do this using the Application global variable. Using Application, you can access a pointer to the application's main form through the MainForm property.

 
Note: About Application
For those of you new to C++Builder, Application is an instance of the TApplication object and an integral part of each C++Builder program. It encapsulates the methods and attributes prescribed by Windows to qualify as a Windows application, including the processing of Windows messages. Application is defined globally in all C++Builder programs and can provide you with many useful values (check out TApplication's properties in the online VCL help for more information).

Let's look at a short example that shows you how to declare and set a pointer to your main form. You can follow along by creating a new C++Builder application and adding a second blank form. We'll assume that the main form is called TForm1 and that it's declared and implemented in Unit1.cpp and Unit1.h (these are the defaults when you create a new C++Builder application, anyway). Assume also that you want to access the main form (TForm1) from a secondary form called TForm2 (implemented in Unit2.cpp and Unit2.h). To do this, you'll acquire a pointer to TForm1 from

Application->MainForm
Before you can declare a pointer to a TForm1 instance inside of TForm2, however, TForm2's header file (Unit2.h) must include TForm1's header file. You can do this by adding
#include "Unit1.h"
to the list of #include statements already present in Unit2.h, as follows:
//----------------------------------------
#ifndef Unit2H
#define Unit2H
//----------------------------------------
#include 
#include 
#include 
#include 
#include "Unit1.h" // Include Unit1 defs
You can now safely declare a pointer to TForm1 within your TForm2 class declaration:
class TForm2 : public TForm
{
__published:  // IDE-managed Components
    void __fastcall FormShow(
      TObject *Sender);
private:	// User declarations
// TForm1 pointer 
TForm1* myAppsMainForm; 
public:      // User declarations
    __fastcall TForm2(TComponent* Owner);
};
Next, you can set the TForm1 pointer to point to the application's main form when you need it. The code looks like this:
myAppsMainForm = 
    (TForm1*) Application->MainForm;
You can now call any public method or access any public attributes defined in TForm1 from within TForm2 using the myAppsMainForm pointer. You might use the following code, for example, to call a method in the main form asking for identification of the application's user:
String userName =  myAppsMainForm->getUsrName();
To be able to use this call, you'd simply need to add to TForm1 a getUserName() public method that returns a String variable, like this:
class TForm1 : public TForm
{
  __published:  // IDE-managed Components
  private:	// User declarations
  public:       // User declarations
    __fastcall TForm1(TComponent* Owner);
  // Get the user name
    String getUserName(void) { return "Dilbert"; }
};
So far, you've seen how to generate a pointer to an application's main form. You've also used that pointer to call a method and retrieve a value located in the main form. In this case, the method you were calling was contained in the main form's object. Suppose for a moment, however, that the TForm1 object didn't have direct knowledge of the user name, but rather contained a pointer to another object that did. Let's invent this new object and call it UserIdent (you can place this code before TForm1's class definition inside Unit1.h for simplicity):
class UserIdent
{
public:
    UserIdent(String x) { name = x; }
    ~UserIdent() { }
    char* getUserName(void) 
          { return name.c_str(); }
private:
    String name;
};
Now take a look at the changes to the TForm1 class definition:
class TForm1 : public TForm
{
__published:  // IDE-managed Components
private:	// User declarations
public:      // User declarations
     // Pointer to UserIdent object
     UserIdent* userInfo; 
    __fastcall TForm1(TComponent* Owner);
};
We've removed the getUserName() method, adding in its place a new attribute called userInfo that points to an instance of UserIdent. Somewhere within TForm1, you need to instantiate UserIdent and set the userInfo pointer to the new object so you can call it later from TForm2. Here's an example that does just that, shown inside TForm1's constructor:
__fastcall TForm1::TForm1( TComponent* Owner) : TForm(Owner)
{
    userInfo = new UserIdent("Dilbert");
}
Now that you've hidden the getUserName() method inside the UserIdent class, you can no longer access it directly through myAppsMainForm as you did previously. Instead, you now have to reference the userInfo object inside of TForm1 to get the information. That call is as follows:
userName = 
   myAppsMainForm->userInfo->getUserName()
Keep in mind when using this approach that userInfo is a public member of TForm1-meaning that TForm2 can create chaos by changing where userInfo is pointing. A safer approach would be to privatize userInfo and add an accessor to TForm1 to return a pointer to userInfo. Here's a short piece of code showing the private userInfo and a public getUserInfo() method returning the pointer:
Class TForm1 : public TForm
{
__published:  // IDE-managed Components
private:	// User declarations
   // PRIVATE userInfo
   UserIdent* userInfo;  
public: // User declarations
   __fastcall TForm1(TComponent* Owner);
    // Get the pointer to userInfo
   UserIdent* getUserInfo(void) 
              { return userInfo; }   
};
You now need to call the getUserInfo() method of myAppsMainForm to retrieve the pointer to the UserIdent object before calling getUserName(). A quick modification to TForm2 would have the getUserName() method call working again:
userName = myAppsMainForm->getUserInfo()->getUserName();

Switching roles

Let's look at this from another angle for a moment. Suppose TForm2 was the user information expert. Let's make it responsible for gathering the user's name and creating UserIdent. You'll then have TForm2 set the pointer in TForm1 to the valid instance of UserIdent created by TForm2. Check out the new TForm1 class declaration:
class TForm1 : public TForm
{
  __published:  // IDE-managed Components
  private:	// User declarations
    UserIdent* userInfo;// PRIVATE userInfo
  public:       // User declarations
    __fastcall TForm1(TComponent* Owner);
  // Return the pointer
    UserIdent* getUserInfo(void) { return userInfo; }
  // Set the pointer
    void setUserInfo(UserIdent* x) {  userInfo = x; }  
};
Notice that we've added to TForm1's public area a method called setUserInfo(), which accepts a UserIdent pointer and stores it in userInfo. Once TForm2 has collected the user's name and created the UserIdent object, it must call setUserInfo() to send the UserIdent pointer back to TForm1. Here is a snippet from TForm2 that does just that:
void __fastcall TForm2::Button1Click(
  TObject *Sender)
{
    UserIdent *uid = 
        new UserIdent(Edit1->Text);
    myAppsMainForm = 
        (TForm1*) Application->MainForm;
    myAppsMainForm->setUserInfo(uid);
}
The code is an event handler for a button that retrieves the user's name from a text box, instantiates the UserIdent object, and sets TForm1's pointer. Note that you shouldn't delete the UserIdent pointer before you exit TForm2. That responsibility has been shifted to the main form (a task which you'd most likely do in TForm1's OnClose event handler).
Note: A word of caution about pointers
When declaring a pointer that you may have to delete, always set it to NULL somewhere during initialization (in an object's constructor, for example). Calling delete on a NULL pointer is safe…but calling delete on a pointer that you haven't initialized could be disastrous.

Putting it all together

So far, you've seen how you can modify an application's main form to act as a central location for storing data and objects. You've also learned how to get a pointer to the main form, then exploit that pointer to access data and methods stored there. And, we've explained how to create an object in a secondary form and set a pointer to that object in the main form, making that object's services available throughout your application. Now we're going to look at a sample application that uses all the techniques we've covered. The application consists of three forms: a main form called SampleAppForm and two secondary forms called IdentifyUserForm and DemoForm. During initialization, SampleAppForm creates an instance of an AppConfig class that contains some information about the program and its environment, including the fully qualified name of the executable, the path where the executable resides, and the names of a couple of other directories where the program might want to store some information (called dat and backup). Listing A shows the SampleAppForm implementation in Unit1.h and Unit1.cpp. Notice that Unit1.h also contains the declarations for the UserIdent and AppConfig classes.

Listing A: SampleAppForm

// Unit1.h
//----------------------------------------
#ifndef Unit1H
#define Unit1H
//----------------------------------------
#include 
#include 
#include 
#include 

//----------------------------------------
class UserIdent
{
public:
    UserIdent(String x) { name = x; }
    ~UserIdent() { }
    char* getUserName(void) 
{ return name.c_str(); }
private:
    String name;
};

//----------------------------------------
class AppConfig
{
public:
    AppConfig()
    {
       exeName = Application->ExeName;
       exePath = ExtractFileDir(exeName);
       exeDrive = ExtractFileDrive(exeName);
       datPath = exePath + "\\dat";
       bkpPath = exePath + "\\backup";
    }
    ~AppConfig() { }
    char *getExeName(void) 
{ return exeName.c_str(); }
    char *getExePath(void) 
{ return exePath.c_str(); }
    char *getExeDrive(void) 
{return exeDrive.c_str(); }
    char *getDatPath(void) 
{ return datPath.c_str(); }
    char *getBkpPath(void) 
{ return bkpPath.c_str(); }
private:
    String exeName, exePath, exeDrive, datPath, bkpPath;
};

//---------------------------------------
class TSampleAppForm : public TForm
{
__published:  // IDE-managed Components
    TButton *Demo;
    TButton *Quit;
    void __fastcall FormShow(TObject *Sender);    
    void __fastcall DemoClick(TObject *Sender);
    void __fastcall QuitClick(TObject *Sender);
    void __fastcall FormClose(TObject *Sender, 
      TCloseAction &Action);
private:	// User declarations
    // Pointer to UserIdent object to be 
    // supplied by IdentifyUserForm
    UserIdent* userInfo;
    // Pointer to AppConfig object to be
    // created in SampleAppForm's constructor
    AppConfig* appInfo;
public:      // User declarations
    __fastcall TSampleAppForm(TComponent* Owner);
    // Get and set UserIdent pointers
    UserIdent* getUserInfo(void) 
{ return userInfo; }
    void setUserInfo(UserIdent* x) 
{ userInfo = x; }
    // Get the AppConfig pointer
    AppConfig* getAppConfig(void) 
{ return appInfo; }
};
//----------------------------------------
extern PACKAGE TSampleAppForm *SampleAppForm;
//----------------------------------------
#endif

// Unit1.cpp
//----------------------------------------
#include 
#pragma hdrstop

#include "Unit1.h"
#include "Unit2.h"
#include "Unit3.h"
//----------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TSampleAppForm *SampleAppForm;
//---------------------------------------
__fastcall TSampleAppForm::TSampleAppForm(TComponent* Owner)
    : TForm(Owner)
{
    appInfo = new AppConfig();

    // Next line sets userInfo to NULL. If we try to delete 
    // appInfo without having set it, the app could fail.
    userInfo = NULL;   // Just for safety
}
//---------------------------------------
void __fastcall TSampleAppForm::FormShow(TObject *Sender)
{
    // Before we see the main form, collect user info and 
    // create TForm1's UserIdent object
    IdentifyUserForm->ShowModal();
}
//--------------------------------------

void __fastcall TSampleAppForm::DemoClick(TObject *Sender)
{
    // Show off the objects we're accessing from the main form.
    DemoForm->ShowModal();    
}
//--------------------------------------

void __fastcall TSampleAppForm::QuitClick(TObject *Sender)
{
    Close();    
}
//--------------------------------------

void __fastcall TSampleAppForm::FormClose(TObject *Sender,
      TCloseAction &Action)
{
    // Delete the memory we used
    delete userInfo;
    delete appInfo;    
}
//--------------------------------------

After SampleAppForm initializes, it opens an instance of IdentifyUserForm, as shown in Figure A.

Figure A: The main form opens the subform IdentifyUserForm.
[ Figure A ]

Listing B contains the source code for IdentifyUserForm. It collects a name, creates a UserIdent object, and then sets SampleAppForm's UserIdent pointer to that object by calling setUserInfo(). Once IdentifyUserForm closes, the user can open DemoForm by clicking the Demo button located on SampleAppForm, as shown in Figure B.

Figure B: You can click the Demo button on the main form to open DemoForm.
[ Figure B ]

Listing B: IdentifyUserForm

// Unit2.h
//---------------------------------------
#ifndef Unit2H
#define Unit2H
//---------------------------------------
#include 
#include 
#include 
#include 
#include "Unit1.h"
//----------------------------------------
class TIdentifyUserForm : public TForm
{
  __published:  // IDE-managed Components
    TEdit *Edit1;
    TButton *Ok;
    TLabel *Label1;
    void __fastcall OkClick(TObject *Sender);
    
  private:	// User declarations
    // Local UserIdent pointer. Before this form closes, we'll 
    // pass this pointer to the main form.
    UserIdent *uid;
    TSampleAppForm* sampleAppForm; // Main form
  public:    // User declarations
    __fastcall TIdentifyUserForm(TComponent* Owner);
};
//---------------------------------------
extern PACKAGE TIdentifyUserForm *IdentifyUserForm;
//---------------------------------------
#endif


// Unit2.cpp
//---------------------------------------
#include 
#pragma hdrstop

#include "Unit2.h"
//----------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TIdentifyUserForm *IdentifyUserForm;
//---------------------------------------
__fastcall TIdentifyUserForm::TIdentifyUserForm(
  TComponent* Owner) : TForm(Owner)
{

}
//----------------------------------------
void __fastcall TIdentifyUserForm::OkClick(TObject *Sender)
{
    uid = new UserIdent(Edit1->Text);
    sampleAppForm = (TSampleAppForm*)Application->MainForm;
    sampleAppForm->setUserInfo(uid);
    Close();
}
//---------------------------------------
Listing C contains the source code for DemoForm. The form contains one text area and several buttons that retrieve information from one of the objects contained in the main form. As you can see in Figure C, each button's purpose is clearly labeled. The User   Name button, for example, calls upon TForm1's UserIdent object to supply the user's name. The Exe Drive button calls the TForm1 AppConfig object's getExeDrive() method to retrieve the letter of the drive where the executable resides.

Figure C: DemoForm gets information from objects on the main form.
[ Figure C ]

Listing C: DemoForm

// Unit3.h
//----------------------------------------
#ifndef Unit3H
#define Unit3H
//----------------------------------------
#include 
#include 
#include 
#include 
#include "Unit1.h" 
//----------------------------------------
class TDemoForm : public TForm
{
  __published:	// IDE-managed Components
    TEdit *Edit1;
    TButton *UserName;
    TButton *ExePath;
    TButton *ExeDrive;
    TButton *DatPath;
    TButton *Done;
    void __fastcall FormShow(TObject *Sender);
    void __fastcall UserNameClick(TObject *Sender);
    void __fastcall ExePathClick(TObject *Sender);
    void __fastcall ExeDriveClick(TObject *Sender);
    void __fastcall DatPathClick(TObject *Sender);
    void __fastcall DoneClick(TObject *Sender);
  private:  // User declarations
    TSampleAppForm* sampleAppForm; // Main form
  public:   // User declarations
    __fastcall TDemoForm(TComponent* Owner);
};
//----------------------------------------
extern PACKAGE TDemoForm *DemoForm;
//----------------------------------------
#endif


// Unit3.cpp
//----------------------------------------
#include 
#pragma hdrstop

#include "Unit3.h"
//----------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TDemoForm *DemoForm;
//--------------------------------------
__fastcall TDemoForm::TDemoForm(TComponent* Owner) : TForm(Owner)
{
}
//----------------------------------------
void __fastcall TDemoForm::FormShow(TObject *Sender)
{
  // Set up the pointer to our main form
  sampleAppForm = (TSampleAppForm*) Application->MainForm;
}
//----------------------------------------
void __fastcall TDemoForm::UserNameClick(TObject *Sender)
{
  Edit1->Text = sampleAppForm->getUserInfo()->getUserName();
}
//----------------------------------------
void __fastcall TDemoForm::ExePathClick(TObject *Sender)
{
  Edit1->Text = sampleAppForm->getAppConfig()->getExeName();
}
//----------------------------------------
void __fastcall TDemoForm::ExeDriveClick(TObject *Sender)
{
  Edit1->Text = sampleAppForm->getAppConfig()->getExeDrive();
}
//----------------------------------------
void __fastcall TDemoForm::DatPathClick(TObject *Sender)
{
  Edit1->Text = sampleAppForm->getAppConfig()->getDatPath();
}
//----------------------------------------
void __fastcall TDemoForm::DoneClick(TObject *Sender)
{
  Close();    
}
//----------------------------------------

Conclusion

You're now armed with techniques that give you the ability to share methods and attributes between forms, making it possible for you to create more efficient and tightly integrated applications. Don't be fooled by the simplicity of the objects we worked within this article-you can easily extend these techniques to handle much more complex applications.