HOME - - - - - Delphi Tutorials TOC - - - - - - Other material for programmers

Delphi tutorial: Where To Put Things (Level 2)

This has good information, and a search button at the bottom of the page

Please don't dismiss it because it isn't full of graphics, scripts, cookies, etc!

Click here if you want to know more about the source and format of these pages.


Once you have got started with Delphi, you will sometimes know what you need to add to a unit, but have trouble getting it in the right place. This started as an answer to that, but as I wrote, I got too heavily into another issue! I will try to re-edit this into more logical pieces one day... but rest assured... this does eventually get to 'Where to put things', even if there is a (useful, I think) detour along the way!!! I think this tutorial is less well structured than others, but I also think that there is important information in it, worth the effort you will have to make to extract it. What do you think? (Both issues!)

This discussion follows on from the level 1 tutorial called 'The Whole Picture', but you don't need to read that now. That talks about all the elements of a Delphi project. This restricts its attention to units... the 'thing' you see when working on a form and the program code which determines its behaviour.

In syntax notation form (see below) a basic unit is nothing more than....

{unit heading} ; {interface part} {implementation part } end .

(I hope that whets your appetite for learning about syntax notation? And, by the way, a complex unit is not much more complicated.)

Keep the basic 'skeleton' clear in your mind, and you will progress faster.

The following is an almost empty, parts named-by-Delphi-defaults unit. I have added the reference points, (*1*), (*2*), etc.

.unit Unit1;(*1*)
.interface (*2*)
. SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
. Forms, Dialogs, StdCtrls;(*3*);
.type (*4*)
. TForm1 = class(TForm)
. Label1: TLabel;
. procedure Label1Click(Sender: TObject);(*5*)
. private
. (* Private declarations *)(*6*)
. public
. (* Public declarations *)(*7*)
. end;(*8*)
. Form1: TForm1;(*9*)
.{$R *.DFM}(*11*)
.procedure TForm1.Label1Click(Sender: TObject);


This program would need a simple form. The form would have a label called Label1, which initially said 'Hi'. When you run the program, all it does is change the 'hi' to 'Clicked' if you click on it.

To write such a program using Delphi, the programmer would set up the form, and then add the single line

. label1.caption:='Clicked';

Prior to the programmer's typing 'efforts', clicks of his/her mouse (object inspector, label1 events, OnClick) would have added (near the bottom) the....

.procedure TForm1.Label1Click(Sender: TObject);

... and (near the top), the....

.procedure Label1Click(Sender: TObject);

The 'Label1: TLabel;' would have been added when the label was added to the form.

All of the rest of what you see in the 'Simple Delphi Program' listing is common to all Delphi units! It is the 'lines' on a 'blank' page.
Much of your Delphi programming is as simple as adding the...

. label1.caption:='Clicked';

to the program above. Delphi takes care of all sorts of things for you, you just add 'what to do' between the Delphi supplied 'begin' and 'end'
A short digression....

Help on help... This bit ought to ba Level 1 mini tutorial... for for now it goes here! The following assumes you haven't used the 'find' tab of the help file viewer... and may still be relevant, even if you have.

Imagine you are working on some program code. You are about to use a word, and you can't quite remember the details. Type the word. You can leave the cursor at the end of the word. Press F1. The computer will either bring up a page of help, or a dialog giving you several choices. All very like using Help files in general, BUT!! BEWARE!! With Delphi there are a number of different help files. It is easy to get yourself into ONE of them, and get the impression that there is no help on what you want to know. The title on the help window you are viewing will tell you which help file you are in. If you suspect that you are in the wrong help file, go back to the unit page you were on, put something different in, or just move the cursor, and press F1 again. If you get the 'Search All window, you are 'up' to a more global level, and are looking at all of the Delphi help files for whatever you need. When you select an item from the list, you will frequently see a number of items relating to that topic. After each, in brackets, you'll see an indication of which help file that entry would take you to.

You will find reading many of the help file entries easier and more productive once you understand the syntax notation system (described here) which computer people have evolved over the years to allow concise, unambiguous and comprehensive descriptions of what is and is not allowed in a language.

An enorourmous part of Delphi and Pascal (all of it, I suppose... certainly most of it) consists of blocks.

A block is...

{declaration part}{statement part}

Often, Delphi will take care of the declaration part for you. (The words in this section are all good search keys in the help files, by the way.)

The statement part is.....

{simple statement}
{compound statement}

Simple statements are things like....


if c1= 5 then c1:=3 else c1:=5



A compound statement is....

begin {statement} :+ ;{statement}+: end

Thus, the following ar legal compound statements:

begin c1:=5 end

if c1=5 then c1:=3 else c1:=5;

Happily, a statement can be nothing! This is happy, because it allows the following to be legal, too:

if c1=5 then c1:=3 else c1:=5;

There is an aditional semicolon in this version of the previous example... the one on the showmessage line. Unless a statement could be nothing, that semicolon would be illegal.

LEARN THE ALLOWED SYNTAX. LEARN TO SEE THINGS IN SYNTAX NOTATION TERMS. If you do, many, many, many tedious hours of trying to get some piffling little thing right will be saved.

Another, related, tip: If you're really stuck with something, print out the offending section. Draw circles around the parts of what you have, starting with the innermost structures. The circles should never cross... you will end up with circles (one or more) withing circles, within circles in a properly constructed Pascal / Delphi program. If you see places where the circles do cross, you will be looking at the places where your attempt at a solution has a problem!

And now, at last... Where to put things!!!

To create exemplary situations, I am going to create a tiny application with two forms. You don't need two forms for many things, but there's no harm in introducing some of the issues now.

First of all (perhaps outside Delphi), set up an empty folder. Start a new project. We'll not bother with fancy names for things, just use the Borland defaults. I'm doing this in Delphi 2, by the way... Delphi 1 users shouldn't find too many differences.

On the form that Delphi gives you, create a label. Caption it 'Hi'.

Save what we have so far.

Create a second form (File|New Form). Put a label on it. Caption it 'Bye'.

Create an OnClick handler for form1 which says...


Run the project. You'll get a message saying..

form1 references form2... not in Uses list... add?

Answer 'yes'.

This will put...

uses Unit2;

... into unit1.pas just after the word 'implementation', my refernce point (see simple program above) (*10*)

We've added something! We've seen where it should go! Whenever you can, leave the work to Delphi... but be sure you understand what is going on!

Run the program. Click on Hi. The first form will disappear, the second will appear. Click on the little x in the upper right of the form and it will disappear... but your application is still runnning!!! (The first form, though not currently visible, is still present on your machine.) Happily, there is an icon on the taskbar which you can right click. Then click close.

For both forms, add an OnDestroy event handler consisting of...


((PROBLEM::: At this stage, I am pretty sure that the application closed properly when I clicked the little x in the upper right of either form. Later, the application went back to NOT closing when I clicked on the little x of the second form. Do you get the same thing? Do you know why, how to fix it? ('Email me' button at bottom!) Sorry about this loose end...)))

I like my programs to display a version number. We're going to set up a constant in unit1.pas which will be available to both of the forms in the project.

In unit1.pas just after my refernce point (*3*) add...

const ver='1.00';

(Put it in apostrophes or Delphi will consider it to be type currency, and you want to use it as a string.)

Add another label to form1. Name it laVer

Add a OnCreate event handler to form1, and make it....

laVer.caption:='Version number: '+ver;

Add another label to form2. Name it laVer (Notice we CAN use the same name as we used earlier?)

Add a OnCreate event handler for form2, and make it....

laVer.caption:='Version number: '+unit1.ver;

When you compile, you'll get an error message. This is because unit2 doesn't know about the constant declared in unit1 unless you tell unit2 to use unit1. You do this by adding...

uses unit1;

just after my reference point (*10*)

Why don't you put it up with the other uses items in the list ending just before my (*3*)? You could.. it will work, if put there. However, to help your programs to develop smoothly, you try to keep the scope of things no more extensive than necessary. (This is a big topic, worthy of a tutorial in itselt.. but for now, just take my word for it!) The const ver='1.00'; had to go up in the interface section of unit1 so that unit2 could 'see' it. Other units do not need to see that unit2 uses unit1, so that is placed down in the implementatino section.

Now we're going to look a bit at where and how procedures or functions should be added to Delphi units. Just bear with me? We'll have a little 'learning by doing.' At first, some of the things I'm doing will seem like overkill... but reasons will emerge. We will start with a little inches to meters converter.

Set up an empty folder. Start a new project. Let Delphi pick names, except as noted. Save the new project to the empty folder.

Put two labels on the form, side by side. Name them laInchesHeader and laMetersHeader. Caption them Inches and Meters

Under the first, put an edit box named edInchesInput, with text 0.

Under the other, put a label named laMetersOutput, with caption 0.

Double click on the edit box. Delphi will prepare most of the following, and the required forward declartion near the start of the unit. You will need to enter the suff between the 'begin' and the 'end'.

. procedure TForm1.edInchesInputChange(Sender: TObject);
. begin
. laMetersOutput.caption:=
. floattostr(strtofloat(edInchesInput.text)/39.37)
. end;

Get that much working. NB/Beware: You need to keep the contents of the edit box a valid number at all times to avoid an error our program is not robust enough to cope with.

Test data: 39.37 inches is one meter. 100 inches is about 2.5 meters.
Once that is working, change it to the following:

. procedure TForm1.edInchesInputChange(Sender: TObject);
. function Convert(si1:string;si2:single):string;
. begin
. Convert:=floattostr(strtofloat(si1)/si2);
. end;
. begin
. laMetersOutput.caption:=Convert(edInchesInput.text,39.37)
. end;

This is essentially the same as what went before, but we have created a 'new word in the language', 'Convert'. It is a function. In other words, when the program executes, and it comes to the line....

. laMetersOutput.caption:=Convert(edInchesInput.text,39.37)

everything to the right of the := simply results in a string. The string is calculated from whatever is in edInchesInput.text at the time, and the 39.37 is used in the calculation.

Now add three more labels and one more edit box to your form, again in a square. This part of the form will be used for converting between miles and kilometers. Name the edit box edMilesInput and the lower right label laKMOutput. Double click on the edMilesInput editbox, and build the following. (You can do most of it with a simple select/copy/paste)....

. procedure TForm1.edMilesInputChange(Sender: TObject);
. function Convert(si1:string;si2:single):string;
. begin
. Convert:=floattostr(strtofloat(si1)/si2);
. end;
. begin
. laKMOutput.caption:=Convert(edMilesInput.text,39370/(5280*12))
. end;

Now... it should occur to you that the two Convert functions present several puzzles:

1)Why have two identical things in the unit?

2)How does the program know which convert to use?

Answer to Q1: There is no good reason... other than this is a way to lead you to a more satisfactory answer.

Answer to Q2: As the program stands now, each 'Convert' is LOCAL to a different procedure.

From either of the procedures, select all of....

. function Convert(si1:string;si2:single):string;
. begin
. Convert:=floattostr(strtofloat(si1)/si2);
. end;

Press ctrl-X... that is a quick way to do Edit|Cut

Move to just after the

. {$R *.DFM}

...which is just after the word 'implementation'

Press ctrl-V.. that is a quick way to do Edit|Paste.

Find the Function Convert... /si2);end; stuff in the other procedure and cut it out. be very careful to leave the right 'end's in place! The implementation part of your unit should now consist of....

. implementation
. {$R *.DFM}
. function Convert(si1:string;si2:single):string;
. begin
. Convert:=floattostr(strtofloat(si1)/si2);
. end;
. procedure TForm1.edInchesInputChange(Sender: TObject);
. begin
. laMetersOutput.caption:=Convert(edInchesInput.text,39.37)
. end;
. procedure TForm1.edMilesInputChange(Sender: TObject);
. begin
. laKMOutput.caption:=Convert(edMilesInput.text,39.37*1000/(5280*12))
. end;
. end.

Get it working again!

See how one function can be shared by two parts of your unit? This not only saves you typing, but there is only ONE place where errors can hide.
Confession: I couldn't think of a plausible excuse to do the following in the example we have developed this far. Trust me, though... there will be times when you need the following techniques.

Suppose you wanted to refer to what is in edMilesInput.text from within the Convert function. For the sake of this tutorial, add the two lines needed to make Convert into the following. (It won't compile.)

. function Convert(si1:string;si2:single):string;
. var siTmp:single;
. begin
. siTmp:=strtofloat(edMilesInput.text);
. Convert:=floattostr(strtofloat(si1)/si2);
. end;

(We never use siTmp, so in a real program the line would be pointless... but there will be times when you want to use such a thing.)

If you try to compile it, you'll get an 'undeclared identifier' error message (Delphi 2. Similar, or same in other Delphis, I would expect.)

This is because the edMilesInput object is unknown to Convert. EdMilesInput is outside Convert's scope.

There are two solutions.

You can tell Convert where to find the object by extending the name.

. siTmp:=strtofloat(Form1.edmilesinput.text);

will compile just fine. The only change is putting Form1. in front of the name of the object. We've left Convert outside the Form1 object, and accessed part of Form1 through explicit naming.

The alternative is to make Convert part of Form1. That requires two changes. The function declaration becomes..

. function TForm1.Convert(si1:string;si2:single):string;

(We've merely added the TForm1. (NB: *T*Form1 this time, merely Form1 (No T) under the other approach.)

It is also necessary to include a copy of the above (one line only) in the interface part of the unit. As it should be an exact copy, it is sensible to use select/copy/paste to create the copy. (The more you use s/c/p, the more fluent you will become with this useful toll of RAD)

The copy should go just after the...

. private
. { Private declarations }

You will only need to put things in the 'public' part when you are dealing with more complex projects, by which time you'll realise when to use the alternative. Putting things in the 'private' section restricts their scope... always a good idea. Never give anything more scope than it needs.

More on scope...

As you know, you cannot use a variable unless you have declared it. In the final stages of the exercise above, we delcared siTmp (data type 'single') in the function Convert. We only needed it within Convert, so it made sense to declare it within Convert. It cannot be used outside of convert... nor (which is more the point) can it have any effect outside of Convert. As far as possible restrict the scope of variables.

An exception: I don't think I ever wrote a program which didn't have an sTmp variable. The 's' says, to me and to anyone using the same conventions, that this is a string-type variable. (Delphi doesn't care what you call your variables... but you will, when you try to read your programs!) The Tmp warns me that anything I put in this variable is there temporarily.. I shouldn't, don't need to, worry about putting new things in it, because I've agreed with myself not to put anything there that needs to be there many lines later. Anywhere an sTmp appears in my code, it should have been initialised only a few lines previously. The following is a pretty typical use of sTmp for me. (The code converts the number in score to a string, and puts spaces in front of it to make the string at least three characters long... useful for formating text in a monospace font.)

. sTmp:=IntToStr(bScore); (*Score is a variable with a value in it*)
. if bScore<10 then sTmp:=' '+sTmp;
. if bScore<100 then sTmp:=' '+sTmp;

With such a a variable, I don't mind giving the whole program a single sTmp which all parts of the program can share, using and re-using it as often as need be. To make a variable available to a large part of the program, you put

. var sTmp:string;

just after the {$R *.DFM} at the start of the implementation part of the program.

An exception to the exception: loop counters need to be declared within the procedure or function which use them. I would like to declare a c1 similar to the sTmp just discussed, for things like the following which makes a (weakly) encoded copy of a string...

. sTmp:='';
. for c1:=1 to length(sEnCodeThis) do
. sTmp:=sTmp+chr(ord(sEnCodeThis[c1])-1);

Unfortunetly, you aren't allowed to use a global c1 for this... it needs to be declared locally.
If you have a multi-form project, and you want something in a variable, say bScore, in one form, say Form1, to be available to another form, say Form2, you need to put

. bScore:byte; (*NB NOT var bScore:byte;*)

just after

. public
. { Public declarations }

in the interface part of Form1. When you have done that, then in Form2 you can access bScore by calling it Form1.bScore. (You might want to read that as "Form1's bScore".)


The End

Sorry... as I said at the start, I know this tutorial is badly organised, but it does present some improtant information. I hope you will feel that whatever effort you have to make is rewarded.

=== notes to myself...

=== Important to do: Edit out to own turtorial: 'Syntax Notation'
=== Important to do: Edit out to own turtorial: 'Help with help'
=== Important tutorial to do: Scope.




Was the above of any use? Please send an email, at least. (If you would be interested in more tutorials like this, say so. I'll try to remember to notify you if they become available.) For my idle curiosity... Where are you, please? (City, state, country?)

Even better, download a piece of my software, try it and pass it on to someone?

Cheers, Tom Boyd


   Search this site or the web        powered by FreeFind
  Site search Web search
Site Map    What's New    Search

Click here if you're feeling kind! (Promotes my site via "Top100Borland")
Ad from page's editor: Yes.. I do enjoy compiling these things for you... hope they are helpful. However.. this doesn't pay my bills!!! If you find this stuff useful, (and you run an MS-DOS or Windows pc) please visit my freeware and shareware page, download something, and circulate it for me? Links on your page to this page would also be appreciated!

Click here to visit editor's freeware, shareware page.

Link to Tutorials main page
Here is how you can contact this page's author, Tom Boyd.

Valid HTML 4.01 Transitional Page WILL BE tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org

If this page causes a script to run, why? Because of things like Google panels, and the code for the search button. Why do I mention scripts? Be sure you know all you need to about spyware.

....... P a g e . . . E n d s .....