PDA

View Full Version : Delphi 6 Tutorial (Chapter 14) page 1


_I_Play_Chess
11-29-2003, 11:18 AM
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD colSpan=2>Communicating Between Forms <!-- End of Headline --></TD></TR>
<TR>
<TD bgColor=#cc0000 colSpan=2 height=1><IMG height=1 src="http://images.about.com/all/bullets/dot_clea.gif" width=1 border=0></TD></TR>
<TR>
<TD colSpan=2>Page 1: Finding out how a modal form was closed</TD></TR></TBODY></TABLE>Welcome to the Fourteenth chapter of the FREE online programming course:

In the previous chapter we looked at simple SDI forms and considered some good reasons for not letting your program auto-create forms. This instalment builds on that to demonstrate techniques available when closing modal forms and how one form can retrieve user input or other data from a secondary form.
<H5 style="PADDING-LEFT: 10px; MARGIN-BOTTOM: 0px; COLOR: #000080">Finding out how a modal form was closed</H5>Modal forms offer specific features that we cannot have when displaying non-modally. Most commonly, we will display a form modally to isolate its processes from anything that might otherwise happen on the main form. Once these processes complete, you might want to know whether the user pressed the Save or Cancel button to close the modal form. You can write some interesting code to accomplish this, but it does not have to be difficult. Delphi supplies modal forms with the ModalResult property, which we can read to tell how the user exited the form.

The following code returns a result, but the calling routine ignores it:
<TABLE width="75%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>var</B>
F:TForm2;
<B>begin</B>
F := TForm2.Create(<B>nil</B>);
F.ShowModal;
F.Release;
...
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>The example shown above just shows the form, lets the user do something with it, then releases it. To check how the form was terminated we need to take advantage of the fact that the ShowModal method is a function that returns one of several ModalResult values. Change the line F.ShowModal to <B>if</B> F.ShowModal = mrOk <B>then</B> We need some code in the modal form to set up whatever it is we want to retrieve. There is more than one way to get the ModalResult because TForm is not the only component having a ModalResult property - TButton has one too. Let us look at TButton's ModalResult first. Start a new project, and add one additional form (Delphi IDE Main menu: File -&gt; New -&gt; Form). This new form will have a 'Form2' name. Next add a TButton (Name: 'Button1') to the main form (Form1), double click the new button and enter the following code:
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>procedure</B> TForm1.Button1Click(Sender: TObject);
<B>var</B> f : TForm2;
<B>begin</B>
f := TForm2.Create(<B>nil</B>);
<B>try</B>
<B>if</B> f.ShowModal = mrOk <B>then</B>
Caption := 'Yes'
<B>else</B>
Caption := 'No';
<B>finally</B>
f.Release;
<B>end</B>;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE><IMG height=237 alt="Object Inspector: ModalResult Property" src="http://delphi.about.com/library/graphics/071503_1.gif" width=190 align=left>Now select the additional form. Give it two TButtons, labelling one 'Save' (Name : 'btnSave'; Caption: 'Save') and the other 'Cancel' (Name : 'btnCancel'; Caption: 'Cancel'). Select the Save button and press F4 to bring up the Object Inspector, scroll up/down until you find the property ModalResult and set it to mrOk. Go back to the form and select the Cancel button, press F4, select the property ModalResult, and set it to mrCancel. It's as simple as that. Now press F9 to run the project. (Depending on your environment settings, Delphi may prompt to save the files.) Once the main form appears, press the Button1 you added earlier, to show the child form. When the child form appears press the Save button and the form closes, once back to the main form note that it's caption says "Yes". Press the main form's button to bring up the child form again but this time press the Cancel button (or the System menu Close item or the [x] button in the caption area). The main form's caption will read "No".
<P clear=all>How does this work? To find out take a look at the Click event for TButton (from StdCtrls.pas):
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>procedure</B> TButton.Click;
<B>var</B> Form: TCustomForm;
<B>begin</B>
Form := GetParentForm(Self);
<B>if</B> Form &lt;&gt; nil <B>then</B>
Form.ModalResult := ModalResult;
<B>inherited</B> Click;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>What happens is that the Owner (http://delphi.about.com/library/weekly/aa031301a.htm) (in this case the secondary form) of TButton gets its ModalResult set according to the value of the TButton's ModalResult. If you don't set TButton.ModalResult, then the value is mrNone (by defaolt). Even if the TButton is placed on another control the parent form is still used to set its result. The last line then invokes the Click event inherited from its ancestor class. To understand what goes on with the Forms ModalResult it is worthwhile reviewing the code in Forms.pas, which you should be able to find in ..\DelphiN\Source (where N represents the version number). In TForm's ShowModal function, directly after the form is shown, a Repeat-Until loop starts, which keeps checking for the variable ModalResult to become a value greater than zero. When this occurs, the final code closes the form. You can set ModalResult at design-time, as described above, but you can also set the form's ModalResult property directly in code at run-time. Now, knowing whether the user wants to accept or reject what occurred on the child form, the calling form can react by using the information from the child form or ignoring it. Let's see how...

(to be continued)<!-- END CONTENT AREA B --><!-- Multi-page feature Navigation. Use this only if the feature has multiple pages. -->

_I_Play_Chess
11-29-2003, 11:28 AM
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD colSpan=2>Communicating Between Forms <!-- End of Headline --></TD></TR>
<TR>
<TD bgColor=#cc0000 colSpan=2 height=1><IMG height=1 src="http://images.about.com/all/bullets/dot_clea.gif" width=1 border=0></TD></TR>
<TR>
<TD colSpan=2>Page 2: Getting the values from another form <!-- End of Subhead --></TD></TR></TBODY></TABLE>Now, knowing whether the user wants to accept or reject what occurred on the child form, the calling form can react by using the information from the child form or ignoring it. Let's see how... Events to look at for ModalResult (in Forms.pas): <B>function</B> TCustomForm.ShowModal: Integer;
<B>procedure</B> TCustomForm.CloseModal;
<B>function</B> TCustomForm.CloseQuery: Boolean;
<B>procedure</B> TCustomForm.Close;
ModalResult Constants from Controls.pas <B>const</B>
&nbsp;&nbsp;mrNone = 0;
&nbsp;&nbsp;mrOk = idOk;
&nbsp;&nbsp;mrCancel = idCancel;
&nbsp;&nbsp;mrAbort = idAbort;
&nbsp;&nbsp;mrRetry = idRetry;
&nbsp;&nbsp;mrIgnore = idIgnore;
&nbsp;&nbsp;mrYes = idYes;
&nbsp;&nbsp;mrNo = idNo;
&nbsp;&nbsp;mrAll = mrNo + 1;
&nbsp;&nbsp;mrNoToAll = mrAll + 1;
&nbsp;&nbsp;mrYesToAll = mrNoToAll + 1;
Sometimes you will need to get more than one piece of information back from a form, but let's start simple. The following example shows how to get the value stored in a TEdit (Name : 'Edit1') Text property if the user pressed the Ok button on the child form (Name: 'frmSimpleString').
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><I>// Code for main form</I>
<B>procedure</B> TfrmMain.ModalResultdemo2Click(Sender: TObject);
<B>var</B>
f:TfrmSimpleString ;
<B>begin</B>
f := TfrmSimpleString.Create(<B>nil</B>);
<B>try</B>
if f.ShowModal = mrOk then
ShowMessage(f.Edit1.Text);
<B>finally</B>
f.Release;
<B>end</B>;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>To keep the example short we are just checking for an Ok response and ignoring negative responses. When the user presses the OK button (its ModalResult is set to mrOk) the following code reads the value of an edit control, Edit1, on the called form and then destroys the form. In a real application you might need information from several components on the called form. We could have streamlined the code a little more by adding code to let the user choose whether to continue with closing the form or aborting back into the called form:
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><I>// Code for main form</I>
<B>procedure</B> TfrmMain.ModalResultdemo2Click(Sender: TObject);
<B>var</B>
f:TfrmSimpleString ;
S:String ;
<B>begin</B>
f := TfrmSimpleString.Create(<B>nil</B>);
<B>try</B>
f.GetThatString(S);
ShowMessage(s);
<B>finally</B>
f.Release;
<B>end</B>;
<B>end</B>;


<I>// Code for the called (child) form</I>

<B>type</B>
TfrmSimpleString = <B>class</B>(TForm)
Button1: TButton;
Button2: TButton;
Edit1: TEdit;

<B>private</B>
<I>** Private declarations }</I>
<B>public</B>
<I>** Public declarations }</I>
<B>procedure</B> GetThatString(<B>var</B> aString: <B>string</B>);
<B>end</B>;

<B>var</B>
frmSimpleString: TfrmSimpleString;

<B>implementation</B>

<I>**$R *.DFM}</I>

<B>procedure</B> TfrmSimpleString.GetThatString(<B>var</B> aString: <B>string</B>);
<B>begin</B>
<B>case</B> ShowModal <B>of</B>
mrOk: aString := 'OK' ;
mrCancel: aString := 'Cancel' ;
<B>end</B>;
<B>end</B>;

<B>end</B>.
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>On the called form we add a procedure GetThatString to interrogate the user modally to find out whether the user wants to continue or abort the current operation. For elegance, the Case statement is used rather than a chain of nested IF..THEN..ELSE statements, i.e.
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE>If ShowModal = mrOk then
...
else if ShowModal = mrCancel then
...
else if ShowModal = mrRetry then
...
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>Retrieving a string is simple enough but, often, you will need much more than a simple string. Methods for getting a lot of information back from a called form include using an array, a TList, a StringList, or Records. The logic used to determine if the user wants to return information remains the same, only the means of storing it varies. To show how this might be done, I will use a record to store user input for retrieval once the user presses OK in the called form. To make things interesting, we will use an Array of Records so that multiple pieces of information can be returned from the called form! Before digging into an example, lets go through a short lesson of record types for those programmers that may have not worked with records: "Records on Delphi"

that we already seen before.

(To be continued)

.

_I_Play_Chess
11-29-2003, 11:34 AM
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD colSpan=2>Communicating Between Forms <!-- End of Headline --></TD></TR>
<TR>
<TD bgColor=#cc0000 colSpan=2 height=1><IMG height=1 src="http://images.about.com/all/bullets/dot_clea.gif" width=1 border=0></TD></TR>
<TR>
<TD colSpan=2>Page 3: Moving records of data between two forms </TD></TR></TBODY></TABLE>Back in the "Record in Delphi" article, a sample code was presented demonstrating pressing a button to populate the array of records with dummy information, then display the records in the memo control. This should give you a starting point to working with record arrays. The main thing to remember about the example is that we created a record type, supplied the name of TPerson, then created a local variable called MyPeople which is an array of type TPerson which can hold three (3) rows of information. Let's use the idea to present a data entry form to a user, allow data to be entered about several people before returning to the main form to process it. Use the next piece of code to call the secondary form for the data entry:
<TABLE width="75%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>procedure</B> TfrmMain.PersonRecord1Click(Sender: TObject);
<B>var</B>
f:TForm5;
<B>begin</B>
f := TForm5.Create(<B>Self</B>);
<B>try</B>
<B>if</B> f.ShowModal = mrOk <B>then</B>
<B>if</B> f.PersonCount &gt; 0 <B>then</B>
ShowMessage('The first person is' + #13 + '"'
+ f.PersonArray[0].FirstName + '" "'
+ f. PersonArray[0].LastName + '"');
<B>finally</B>
f.Release;
<B>end</B>;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>When the user invokes the event, the main form creates the data entry form, shows it, then checks to see if there were any people entered into the people array. The variable PersonCount in the data entry form is incremented each time a new person is added to the array of records. The following code is placed into a simple unit (Delphi IDE Main Menu: File -&gt; New -&gt; Unit) so that any form in the project can see the People record along with a constant used to limit how many elements (records) can be used in the array of records.
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE>unit kg_Globals; <I>//saved as <B>kg_Globals</B></I>

<B>interface</B>

<B>uses</B>
Windows,Dialogs,SysUtils,Messages,Classes,Forms,Fi leCtrl;

<B>const</B>
ARRAY_SIZE = 5; <I>{Could be a larger size}</I>

<B>type</B>

<I>{Keep each record member to strings to
make the example code easy to understand}</I>

Tkg_People = <B>Record</B>
FirstName : <B>String</B>;
LastName : <B>String</B>;
Street : <B>String</B>;
City : <B>String</B>;
State : <B>String</B>;
ZipCode : <B>String</B>;
Phone : <B>String</B>;
Email : <B>String</B>;
<B>end</B>;

<B>implementation</B>

<B>end</B>.
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>In the public portion of the called form's declarations we declare an array of records and a variable for keeping track of how many records the user has entered.
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE>...
<B>public</B>
<I>** Public declarations }</I>
PersonArray: Array [0..ARRAY_SIZE] of Tkg_People;
PersonCount: Integer;
...
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>In the Create event of the data entry form the following code is called to clear all edit controls so that the form starts up with a clean slate. Each time the user accepts a screenful of people information the ClearEdit procedure is triggered:
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>procedure</B> TForm5.ClearEdits;
<B>var</B> i:Integer;
<B>begin</B>
<I>** sets each TEdit control's
text property to an empty string }</I>
<B>for</B> i := 0 <B>to</B> ComponentCount -1 <B>do</B>
<B>if</B> (Components[i] is TEdit) <B>then</B>
TEdit(Components[i]).Text := '' ;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>The next code fills a row of data in the people array and increments the row counter. The counter is checked to ensure that the number of rows keeps within the fixed boundaries of the array of records.
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>procedure</B> TForm5.cmdAddPersonClick(Sender: TObject);
<B>begin</B>
<I>** Populate a record in an element in
the array of records with current textbox values }</I>

PersonArray[PersonCount].FirstName := First.Text;
PersonArray[PersonCount].LastName := Last.Text;
PersonArray[PersonCount].Street := Street.Text;
PersonArray[PersonCount].City := City.Text;
PersonArray[PersonCount].State := State.Text;
PersonArray[PersonCount].ZipCode := Zip.Text;
PersonArray[PersonCount].Phone := Phone.Text;
PersonArray[PersonCount].Email := Email.Text;
Inc(PersonCount);

<I>**
Once the maximim elements are used up,
disable this button.

NOTE: ARRAY_SIZE is a user defined constant
created for this demo. The Constant indicates
how many records can be accessed in the array.
}</I>

TButton(Sender).Enabled := PersonCount &lt; ARRAY_SIZE;

<I>**
If we can still add more persons clear
old entries, place focus on "First"
name text control
}</I>

<B>if</B> TButton(Sender).Enabled <B>then</B>
<B>begin</B>
ClearEdits;
First.SetFocus ;
<B>end</B>
<B>else</B>
<I>** Exit time , give them a hint by
placing focus on the exit button }</I>
cmdCloseForm.SetFocus ;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>Using this example you can return just about any type of data, in either single or multiple rows. To recap, you can detect how a modal form was closed and get information back in several ways. Keep in mind there are other ways to accomplish returning information from modal forms.
<H5 style="PADDING-LEFT: 10px; MARGIN-BOTTOM: 0px; COLOR: #000080">Detour time</H5>The last thing a programmer needs when searching for assistance in a help file or manuals is an incorrect example of how to do a particular task. I caught one in D4 after writing this article, to do with "Passing additional arguments to forms", listed under "forms" in Delphi help. Shown below are the key pieces:
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>type</B>
TResultsForm = <B>class</B>(TForm)
ResultsLabel: TLabel;
OKButton: TButton;
procedure OKButtonClick(Sender: TObject);

<B>private</B>

<B>public</B>
constructor CreateWithButton(whichButton: Integer; Owner: TComponent);
<B>end</B>;

<B>constructor</B> CreateWithButton(whichButton: Integer; Owner: TComponent);
<B>begin</B>
<B>case</B> whichButton <B>of</B>
1: ResultsLabel.Caption := 'You picked the first button.';
2: ResultsLabel.Caption := 'You picked the second button.';
3: ResultsLabel.Caption := 'You picked the third button.';
<B>end</B>;
<B>end</B>;

<B>procedure</B> TMainForm.SecondButtonClick(Sender: TObject);
<B>var</B>
rf: TResultsForm;
<B>begin</B>
rf := TResultsForm.CreateWithButton(2, self);
rf.ShowModal;
rf.Free;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>Don't feel bad if you can not figure out what's missing/wrong with the example. This code was posted on Borland's news group as a solution for a posted question. Instead of trying to figure out the errors, look at the correct code:
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>type</B>
TResultsForm = class(TForm)
ResultsLabel: TLabel;
OKButton: TButton;

<B>private</B>

<B>public</B>
constructor Create(whichButton: Integer; Owner: TComponent); <B>reintroduce</B>;
<B>end</B>;

<B>constructor</B> TResultsForm.Create(whichButton: Integer; Owner: TComponent);
<B>begin</B>
<B>inherited</B> Create(Owner) ;
<B>case</B> whichButton <B>of</B>
1: ResultsLabel.Caption := 'You picked the first button.';
2: ResultsLabel.Caption := 'You picked the second button.';
3: ResultsLabel.Caption := 'You picked the third button.';
<B>end</B>;
<B>end</B>;

<B>procedure</B> TMainForm.SecondButtonClick(Sender: TObject);
<B>var</B>
f: TResultsForm ;
<B>begin</B>
f := TResultsForm.Create(2, self);
<B>try</B>
f.ShowModal;
<B>finally</B>
f.Release ;
<B>end</B>;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>
<P clear=all>&nbsp;&nbsp;&nbsp;<B>To the next chapter: A Beginner's Guide to Delphi Programming</B>
This is the end of the Fourteenth chapter, in the next chapter, we'll take a small brake from forms. Delphi Personal edition does not offer database support. In the next chapter, you will find out how to create your own *flat* database and store any kind of data - all without a single data aware component.