Exchanging data between two forms.
There are multiple situations, where your application needs to include more than one form, the most common case being, where the major part of the application
runs in one window, and you want to use another window to do a specific task. For example, in my "Bacteria1" and "Conics" applications, I open a second window,
to display the bacteria growth graph resp. to draw the conic. Sometimes, you may want to use a separate window associated with each of several things, you do in
your application. Thus, in my "Pulleys" program, I use a separate form for each kind of exercise. The advantage of doing so, is that you will not have to change
the controls' properties (visible or not, caption, size and position) depending on what is given and what is asked in the exercise. It also regroups all the code,
relative to a given exercise in a different unit. The disadvantage, of course, is that this considerably increases the size of the executable. A big issue, years
ago, but not really a big deal nowadays, where memory is at affordable price and where, anyway, the operating system itself needs gigabytes of RAM. Another situation,
where you may want to use a second form, is when you want to use all the place of the main window to show the application's result and thus, want to enter the input
data in another window; an example of this is my "DCircuits2" application, where I use a data form, where the user can enter the circuit parameters.
The first thing to do in a multiple form application is to declare the secondary form units in your main unit. This is done the same way,
as you do with any unit. Here the uses clause of my "Bacteria1" program; the unit bacteria1_graph being associated with the form
fBacteria1G (display of the graph) and bacteria1_help being associated with the form fBacteria1H (display of the application help text):
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs,
ExtCtrls, Menus, StdCtrls, PopupNotifier, bacteria1_graph, bacteria1_help;
Doing this, a secondary window may be opened by the Show or ShowModal method. The difference between the two
methods is, that when using ShowModal, the code in the main unit is paused, until the secondary window is closed. Thus, in my "Bacteria1" application, the code
fBacteria1G.Show
lets the main window accessible, when the graph is displayed. This means, that the user may enter new bacteria data (and displaying the graph), without having to close
the graph window. On the other side, in my "DCircuits2" application, I use ShowModal: the application is paused (with the main window being inactive), until the user,
after having entered the transistor data, has closed the data entry window.
If the main unit has access to the secondary unit's form, it has access to this form's controls, their properties and their methods. This makes it possible to directly modify the secondary window's content. Two possibilities for you, to choose from, to code a multiple form application:
- Including all code in the main unit, acting on the secondary form's controls by simply prefixing them with the forms name.
- Including the code related to the secondary form in the secondary unit, passing the data needed to this form (and when there are results, reading them into your main unit, when the task is done).
Accessing controls and variables on the secondary form.
All controls on the secondary form are automatically accessible from the main unit, if the secondary unit is declared with the uses clause. Here some code of the main
unit of my "DCircuits2" application. Depending on the options selected by the user (consider or not the base-emitter voltage), the transistor materials combo boxes
(to choose between silicon and germanium) on the data entry form, called fData, have to be enabled or not, and if so, only for the number of transistors actually
selected on the data entry form.
if mSettingsVoltage.Checked then begin
fData.cbMaterial1.Enabled := False;
fData.cbMaterial2.Enabled := False;
fData.cbMaterial3.Enabled := False;
end
else begin
fData.cbMaterial1.Enabled := True;
if fData.rbTransistors2.Checked or fData.rbTransistors3.Checked then
fData.cbMaterial2.Enabled := True;
if fData.rbTransistors3.Checked then
fData.cbMaterial3.Enabled := True;
end;
Variables of the secondary unit may be accessed the same way, under condition to be declared as public variables. Here some code of my "Conics"
application: When the button "Draw" is pushed on the main form, the conics parameters (entered on the main form), have to be passed to the fGraph form and this form has
to be shown (with the code, that actually draws the conic in the TfGraph.FormActivate method).
// Pass conic parameters to fGraph form
fGraph.sConic := sConic;
fGraph.sEquation := sEquation;
fGraph.rA := rA; fGraph.rB := rB;
fGraph.bInverse := cbInverse.Checked;
fConics.btDraw.SetFocus;
// Show fGraph window (conic will be drawn with actual parameters at window show-up)
fGraph.Show;
To read any user entry data or calculation results from the secondary unit into the main unit, proceed the same way. Example: Reading the transistor characteristics
from the controls on the fData form into variables of the main unit.
fData.ShowModal;
if fData.sButton = 'ok' then begin
if fData.rbTransistors1.Checked then
iCircuit := 1
else if fData.rbTransistors2.Checked then
iCircuit := 2
else
iCircuit := 3;
rVS := StrToFloat(fData.edVoltage.Text);
if fData.edCurrent.Text <> '' then
rIL := StrToFloat(fData.edCurrent.Text)
else
rIL := rVS / StrToFloat(fData.edResistance.Text);
iBeta1 := StrToInt(fData.edBeta1.Text);
iBeta2 := 0; iBeta3 := 0;
if fData.edBeta2.Enabled then
iBeta2 := StrToInt(fData.edBeta2.Text);
if fData.edBeta3.Enabled then
iBeta3 := StrToInt(fData.edBeta3.Text);
sMaterial1 := fData.cbMaterial1.Text;
sMaterial2 := fData.cbMaterial2.Text;
sMaterial3 := fData.cbMaterial3.Text;
...
end;
You may wonder, what's this fData.sButton variable is about. The data entry form has two buttons: If the user pushes btOK, the transistor data should be returned to
the main form and the calculations should be done; if she pushes btCancel, the entry should simply be ignored. As I didn't find a direct way to check which button has been pressed on the secondary form, I proceeded as follows: Creating a public string variable (fData.sButton), that is set
to "ok" in the TfData.btOKClick method and is set to "cancel" in the TfData.btCancelClick method. Clicking any of these buttons closes the data entry window and returns
the control to the main unit. To know, if btOK or btCancel was pushed, just read the value of the sButton variable...
Passing data to a secondary form at application start.
In my Luxembourg1 application, I read the data, concerning the Luxembourger cantons and townships from a text file. The array with the canton data should be passed to the fEmblems form, that is used to display the emblem images, the user has to choose from the one, that corresponds to the canton name displayed during the canton emblem quiz. Such tasks are part of the application initialization and should be done at application start, i.e. placed in the TfLuxbg1.FormCreate method. Have a look at the screenshot below. Do you guess, what I did wrong and why this external error occurs?
There isn't any error in the code, but the code is placed in the bad method! Passing data to a secondary form can't be coded in the main unit's
FormCreate method!. The reason is simple: The different forms of an application are created one after the other, starting with the main form. Creating the form
consists of executing all code in the FormCreate method, thus in our example, including the data passage to the secondary form. You see now, what's the problem? With
the data passage code in the FormCreate method, you try to access the public array on the fEmblems form at a moment, where this form does not yet exist. Thus, obvious
that this is not possible!
A simple work-around is to place code, that passes the data, into the FormActivate method. The (main) window only shows up, when all forms have
been created, thus, accessing the fEmblems.aCantons array, when the main form is activated, doesn't cause any problem. A little issue, however: The initialization
of the cantons array on the fEmblems form should be done at application start and not every time, when the window with the emblem images closes and the main window
becomes active again. As for the button, I did not find a direct way to determine application start-up and so I used, here too, a help variable. Setting the Boolean bStart
to True in the FormCreate method and executing the code in the FormActivate method only if bStart actually is True, that's what we want, isn't it? Provided, that we
don't forget to set bStart to False, after the array on fEmblems has been initialized. Here's the code:
procedure TfLuxbg1.FormCreate(Sender: TObject);
begin
ReadTownships(aCantons, aTownships);
SetLength(bTownshipsDone, Length(aTownships));
bStart := True; // start of application flag (used in FormActivate method)
Randomize;
mQuiz1.Click;
end;
procedure TfLuxbg1.FormActivate(Sender: TObject);
begin
if bStart then begin
fEmblems.aCantons := aCantons;
bStart := False;
end;
end;
Note: You can also use the work-around with the Boolean variable to set the focus to a given control at application start. In fact, here too, you may encounter the problem, that this control is not yet ready to be accessed (and you get a "Can't focus" error, when running the application).
If you find this text helpful, please, support me and this website by signing my guestbook.