PDA

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


_I_Play_Chess
11-22-2003, 12:10 PM
Page 1


<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD colSpan=2>Making Forms Work - a Primer <!-- 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: The place where forms are created... <!-- End of Subhead --></TD></TR></TBODY></TABLE>

Welcome to the <B>thirteenth chapter</B> of the FREE online programming course:

In just about every Delphi application, we use forms to present and retrieve information from users. Delphi arms us with a rich array of visual tools for creating forms and determining their properties and behaviour. We can set them up at design time using the property editors and we can write code to re-set them dynamically at runtime. Those of us who have used other visual programming languages such as Microsoft VB (visual Basic )appreciate Delphi's many features for working with forms. However, even with all the creature comforts built into Delphi, there are many things we might want to do which cannot be solved by simply dropping a component onto a form and setting its properties. Over the next several months I will show you some of the simple and more complex tricks for making forms work more efficiently.
<H5 style="PADDING-LEFT: 10px; MARGIN-BOTTOM: 0px; COLOR: #000080">Starting Simply</H5>To get things rolling, let's start off at the simple end with SDI (Single Document Interface) forms. An SDI application normally contains a single document view - as opposite to MDI (multiple document interface). By defualt, every time you create a new (Windows) application in Delphi, you start working in SDI application mode.

Starting a new Delphi project generates and displays the first form in the project file. To view the project file code, from the IDE menu go to Project | View Source. The line we are interested in is painted red:
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>program</B> Project1;

<B>uses</B>
Forms,
Unit1 in 'Unit1.pas' {Form1};
**$R *.res}
<B>begin</B>
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
<B>end</B>.
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>The method CreateForm prepares the form to be displayed as the program's main form. The form is not displayed until the next line executes:
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE>Application.Run;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>Executing CreateForm utilizes memory and system resources. This is not a problem with an application with a single form, but if the project has many forms and they are all created at program startup you may run into low resource problems down the road. Your development computer probably has plenty of memory installed but the situation may be quite different on users' machines, where your application has to run with much more limited resources and in competition with other applications. The solution is simple: <B>take control of form creation and destruction</B>.
<H5 style="PADDING-LEFT: 10px; MARGIN-BOTTOM: 0px; COLOR: #000080">Taking Control</H5>Let's look at the basics for controlling forms in a new project. Imagine you have an application that has a main form (frmMain) with two subsidiary forms to add a new customer (frmAddNewCustomer - Unit2) and edit customers (frmEditCustomer - Unit3).

To add two new forms in the project, simply point to File | New | Form. This will create (and add) a new form named Form2 (+ Unit2) to the project. Rename this form (using the Object Inspector) to 'frmAddNewCustomer'. Repeat the steps for the third form, change the name to 'frmEditCustomer' (leave the name of the unit to be 'Unit3'). When you define the two subsidiary forms Delphi places the following code into the project file:
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>begin</B>
Application.Initialize;
Application.CreateForm(TfrmMain, frmMain);
Application.CreateForm(TfrmAddNewCustomer, frmAddNewCustomer);
Application.CreateForm(TfrmEditCustomer, frmEditCustomer);
Application.Run;
<B>end</B>
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>Each Application.CreateForm... line creates the form object specified by the variable in the second argument as an instance of the class specified in the first. If you leave the code as it is, all three forms will be created in order. The first form in the list (named frmMain here, but it could be any name) is designated as the main form for the project and will be visible once Application.Run has executed. To show either of the other forms you would to apply code to some user-generated event to "show" it (i.e. bring it to the top):
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE>frmAddNewCustomer.Show;
<I>//or</I>
frmAddNewCustomer.ShowModal;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>Ok, so what are the diferences between the Show and ShowModal methods? <!-- END CONTENT AREA B --><!-- Multi-page feature Navigation. Use this only if the feature has multiple pages. -->


&nbsp;

_I_Play_Chess
11-22-2003, 12:13 PM
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD colSpan=2>Making Forms Work - a Primer <!-- 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: Showing Modal forms - where the application can't continue to run until the (modal) form is closed.</TD></TR></TBODY></TABLE>Since we now know how to add more forms to a Delphi (SDI) project, let's see how to call some of those forms dynamically at run time...
<H5 style="PADDING-LEFT: 10px; MARGIN-BOTTOM: 0px; COLOR: #000080">Modal Delphi Forms</H5>Since it is very unlikely that this application would need to have all three forms showing at once, it makes no sense to keep them there hidden and using resources unnecessarily. Our program should create the Add or Edit form only when needed! For example, to display the Add Customer form from the main form: From the Delphi IDE main menu, File -&gt; Use Unit, select Unit2 (frmAddNewCustomer). Note that after this action, 'Unit2' is added to the implementation uses clause (just below the word implementation) of the Unit1 (main form). Remove the following line from the project file (delete it manually or use Project Options in the IDE):
<TABLE bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE> Application.CreateForm(TfrmAddNewCustomer, frmAddNewCustomer);
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>Place a TButton component (Name: 'cmdAddNewCustomer') on the form, double click the left mouse button to generate an OnClick event. This drops us down to the code editor were you would amplify the supplied code framework to show the Add New Customer form when the user clicks the button:
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>procedure</B> TfrmMain.cmdAddNewCustomerClick(Sender: TObject);
<B>var</B>
f:TfrmAddNewCustomer;
<B>begin</B>
f := TfrmAddNewCustomer.Create(Self);
<B>try</B>
f.ShowModal;
<B>finally</B>
f.Release;
<B>end</B>;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>The first thing needed is a variable (f, in our example) to create an instance of the "Add New Customer" form, whose type is TfrmAddNewCustomer. Using this variable, the "Add New Customer" form can be initialized using the Create method of our form's ancestor, TCustomForm. The single argument required by Create is the&nbsp;Owner of the form.
The above example uses the Self keyword to make the main form the owner. The owner could also be the application itself; or you can specify Nil. Caution is needed with a Nil owner, however. You must take care to destroy the form object yourself, using the Free method. If you try to use Release for a form (or any object) with a Nil owner, your program will crash later with an access violation. Once the form has been created, the method ShowModal displays it in a modal state by using. During the time the form is shown and terminated there may be problems, so placing the Show method into a try…finally exception block guarantees that the Release method is called and resources are released once the main form has terminated. In contrast, Free releases the resources immediately the modal form closes, makeing a safer call if the modal form is likely to be invoked more than once during the program.

_I_Play_Chess
11-22-2003, 12:18 PM
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD colSpan=2>Making Forms Work - a Primer <!-- 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: Showing Non-modal forms - making them visible and bringing to the front of other forms on the screen. <!-- End of Subhead --></TD></TR></TBODY></TABLE>Ok, now we know how to add more forms to a Delphi (SDI) project, and even how to call a modal form, let's see how to call some of those forms in a non-modal fasion...
<H5 style="PADDING-LEFT: 10px; MARGIN-BOTTOM: 0px; COLOR: #000080">A Non-Modal Form </H5>If the secondary form needs to be non-modal, use Show instead of ShowModal. We can't use exactly similar code because the form would show briefly, then disappear, because Release is called right after showing the form. To correct this problem use the code shown below:
<TABLE bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>procedure</B> TfrmMain.cmdAddNewCustomerClick(Sender: TObject);
<B>var</B>
f:TfrmAddNewCustomer ;
<B>begin</B>
f := TfrmAddNewCustomer.Create(Self);
f.Show ;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>Then in the FormClose event for the Add New Customer form add the following (Action caFree frees up all resources for the form and destroys it):
<TABLE bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>procedure</B> TfrmAddNewCustomer.FormClose(Sender: TObject);
<B>var</B>
Action: TCloseAction);
<B>begin</B>
Action := caFree ;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>Whoops, there is a slight problem with the code! Can you see the problem? Since the secondary form is non-modal the user can go back to the main form and press the Add button again. The program tries to create another Add New Customer form with unfortunate results if you have not planned for it! To prevent this from happening first check to see if the form object already exists. If so, simply show it; if not, create the form and then show it. There are various ways to test for an object's existence, for example:
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>procedure</B> TfrmMain.cmdAddNewCustomerClick(Sender: TObject);
<B>var</B>
f:TfrmAddNewCustomer ;
iFound,
i:Integer ;
<B>begin</B>
iFound := -1;
<B>for</B> i := 0 <B>to</B> Screen.FormCount -1 <B>do</B>
<B>if</B> Screen.Forms[i] <B>is</B> TfrmAddNewCustomer <B>then</B>
iFound := i;
<B>if</B> iFound &gt;= 0 <B>then
begin</B>
ShowMessage('Add Customer form already created, will now show it') ;
Screen.Forms[iFound].Show ;
<B>end
else
begin</B>
ShowMessage('Add New Customer form not found, creating...') ;
f := TfrmAddNewCustomer.Create(Self) ;
f.Show ;
<B>end</B>;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>These methods give your program much better control of resources than auto-created forms, with very little hand-coding. There is a slight performance trade-off with dynamically created forms, but a delay is unlikely to be perceptible unless the called forms are very complex and/or your user has an unreasonably slow machine. Caution: When defining forms for dynamic invocation you should be aware of the need to avoid any named reference to the properties of the object whose variable is declared in the interface section. That variable will never be assigned because the calling form uses its own local variable to invoke it. For example:
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>procedure</B> TfrmAddNewCustomer.FormCreate(Sender: TObject);
<B>begin</B>
frmAddNewCustomer.Top := Top + 12 ;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>Using our example, the reference to the variable frmAddNewCustomer would cause an access violation. The code below demonstrates how you need to work with properties of the secondary form
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>procedure</B> TfrmAddNewCustomer.FormCreate(Sender: TObject);
<B>begin</B>
Top := Top + 12 ;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>If for some reason you need an explicit reference to the form object, use the identifier Self in front of the property or method.
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>procedure</B> TfrmAddNewCustomer.FormCreate(Sender: TObject);
<B>begin</B>
Self.Top := Self.Top + 12 ;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>The variable in the interface section of can be safely removed if the form is intended only for dynamic invocation. It is scarcely worth doing, though, since it will be optimized out when the project is compiled. For completeness, here is a method for showing a modeless form.
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>procedure</B> TfrmMain.cmdAddNewCustomerClick(Sender: TObject);
<B>var</B>
f:TfrmAddNewCustomer ;
<B>begin</B>
<B>with</B> TfrmAddNewCustomer.Create(Self) <B>do</B>
<B>begin</B>
Show;
<B>end</B>;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>Once you are finished using the form it can be destroyed by using the following code:
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>procedure</B> TfrmMain.cmdCloseFormClick(Sender: TObject);
<B>var</B>
f:TForm;
<B>begin</B>
f := TForm(FindComponent('frmAddNewCustomer')) ;
<B>if</B> f &lt;&gt; <B>nil then</B>
f.Release
<B>else</B>
ShowMessage('Failed to find it') ;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>All set. Ready to code ... read on and learn about some more from_the_battle_field situations... <!-- END CONTENT AREA B --><!-- Multi-page feature Navigation. Use this only if the feature has multiple pages. -->

_I_Play_Chess
11-22-2003, 12:29 PM
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD colSpan=2>Making Forms Work - a Primer <!-- 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 4: Form created? How to position a secondary form precisely! </TD></TR></TBODY></TABLE>Almost done. After we have added 2 more forms to a Delphi (SDI) project, and have displayed some of them as modall (http://delphi.about.com/library/weekly/aa070203b.htm), and modeless, we are ready for some real world situations (and solutions).
<H5 style="PADDING-LEFT: 10px; MARGIN-BOTTOM: 0px; COLOR: #000080">Tidy Habits</H5>Use FindComponent to determine if the form exists or not. FindComponent returns an object (if it exists) of type TControl. Since the desired type is TForm, we typecast FindComponent's result to cast the object as TForm and assign a TForm variable to grab the result. Theoretically, if FindComponent returns Nil, the form does not exist - or does it? Try placing the two procedures above into a main form. Provide a command button named 'CmdAddNewCustomer' to create the form. Provide another command button 'CmdCloseForm' to close the form. Compile and run the project. Click the 'CmdAddNewCustomer' button to create the form and then close it using the 'CmdCloseForm' button. Now click "cmdAddNewCustomer" twice followed by click "cmdCloseForm" twice. On the first attempt the child form is found, then closed, the second attempt fails! To get around this problem a unique name needs to be assigned to the child form. Select a meaningful name and append an integer to the end of the name i.e. 'AddCustomer_' + IntToStr(iForm) ; The variable iForm would be a private variable of frmMain which you would initialize during the OnCreate event of frmMain. Here is the altered code for creating the subsidiary form:
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>procedure</B> TfrmMain.Button1Click(Sender: TObject);
<B>var</B>
f:TfrmAddNewCustomer ;
<B>begin</B>
Inc(iForm) ;
<B>with</B> TfrmAddNewCustomer.Create(Self) <B>do begin</B>
Name := 'AddCustomer_' + IntToStr(iForm) ;
<I>{The caption will be the name
of the form given above}</I>
Show ;
<B>end</B> ;
en<B></B>d;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>On the first call to this event iForm would be zero, then directly before creating the form we use Inc to increment iForm to one. This gives the next invocation of the form a name of AddCustomer_1, which can be used later to remove any instances of the form:
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>procedure</B> TfrmMain.cmdCloseFormClick(Sender: TObject);
<B>var</B>
f:TForm;
i:Integer;
<B>begin</B>
<B>for</B> i := 1 <B>to</B> iForm <B>do begin </B>
f := TForm(FindComponent('AddCustomer_' + IntToStr(i))) ;
<B>if</B> f &lt;&gt; <B>nil then </B>
f.Release
<B>else</B>
MessageDlg('Failed to locate AddCustomer_' +
IntToStr(i),mtError,[mbOk],0) ;
<B>end</B>;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>
<H5 style="PADDING-LEFT: 10px; MARGIN-BOTTOM: 0px; COLOR: #000080">Right Place, Right Time</H5>One more benefit from creating forms dynamically is the ability to position a secondary form precisely, relative to a control on the parent form. Suppose you need to position the Add New Customer form directly on a node in a TreeView displaying accounts. In this example the Add form is displayed when the user double clicks on the TreeView. First place a TreeView on a main form in a project, select the TreeView, press F11 for the Object Inspector, select the property "Items" and double click the left mouse button. Add several new items and sub-items (optional). Finish up by pressing the "OK" button. Next, select the Events page of the Object Inspector while the TreeView is still selected. Double click on the event OnDblClick and enter the following code (which I shall explain in another article):
<TABLE width="100%" bgColor=#ffffcc border=1>
<TBODY>
<TR>
<TD><PRE><B>procedure</B> TfrmMain.TreeView1DblClick(Sender: TObject);
<B>var</B>
A,
R,
P : TPoint;
TheSelectedNode : TTreeNode;
f:TfrmAddNewCustomer ;
<B>begin</B>
<B>if</B> TreeView1.Selected = <B>nil then</B> Exit;
GetCursorPos(P);
A := TreeView1.ScreenToClient(P);
TheSelectedNode := TreeView1.GetNodeAt(A.x, A.y);
<B>if</B> TheSelectedNode = <B>nil then </B>
<B>raise</B> Exception.Create('Please click on an Item in the TreeView!');
GetCursorPos(R) ;
P := ClientToScreen(Point(A.x,A.y));
f := TfrmAddNewCustomer.Create(Self) ;
<B>try</B>
f.Top := R.y ;
f.Left := R.x ;
f.Caption := 'Add customer to account: "' + TheSelectedNode.Text + '"' ;
f.ShowModal ;
<B>finally</B>
f.Release ;
<B>end</B>;
<B>end</B>;
</PRE></TD><PRE></PRE></TR></TBODY></TABLE>Run the project. Select a node in the TreeView and double click the left mouse button. The secondary form should appear with its top left corner positioned on the node which was double clicked. Its caption identifies the item selected in the TreeView. If you had centered the form on the screen, the specific information concerning the item selected in the TreeView would not be accessible. Again, dynamic creation of forms can be of great service to you! I hope this chapter has given some insight into the techniques Delphi puts at your disposal for taking control of form creation and behaviour when developing SDI forms. Next time, more on working with forms. Until then, have fun!
<H5 style="PADDING-LEFT: 10px; MARGIN-BOTTOM: 0px">Some exercises for you...</H5>What happens when you call (create) a non modal Form3 from Form2, and after that call (create) Form2 from Form3?
<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 thirteenth chapter, in the next chapter, we'll deal with retrieving user input or other data from a secondary form. &nbsp;