HOME - - / - - / - - DELPHI COURSE INDEX - - / - - / - - TUTORIALS INDEX - - / - - / - - Other material for programmers

Delphi Course: Second in series using converters to illustrate various points...

This page is information rich, and a has search button at the bottom of the page.

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



You will probably find the text easier to read if you make your browser window much narrower than usual. You may also want to change your browser's zoom level, to enlarge the text. Opera (at least) lets you change zoom level easily. The text will adapt nicely to the settings you decide give the best results for your needs!

The lines of sample Delphi code in these pages will not "wrap". I.e., if a line is too long to show in the width you have set your browser too, parts of the line will be "off the page". Those lines will still copy/paste properly, at least in Opera. Please feel free to send feedback on the choices I've made! (Will you forgive me for not forcing upon you a column of links on the left and a column of ads on the right?)


In the first lesson, we made a reasonable converter for finding out how many centimeters any number of inches equalled. Fine... as far as it went. In this lesson, you make a better converter. It will convert both ways. It will convert between other units. As it is more powerful, it will need a more sophisticated user interface.

You should learn how to...
--- put a menu into an application

You should learn about....
--- Containers
--- Components which don't have simple manifestations on the form You should more learn about....
--- Begin... End blocks
--- using events

Delphi language:
--- OnKeyPress event

Delphi components:
--- Radio button
--- Menu

You may find typos and rough edges in this. None-the-less, the basic information should be accurate. If something seems wrong, or if you find I've assumed knowledge without explaining it in a previous lesson, please let me know. Please forgive matters of typos, etc. for now. I am not inherently sloppy! The blemishes will be dealt with later.




In this lesson, we will extend our earlier application which converted lengths in inches to lengths in centimeters. First we will make it work either way. Then we will provide other conversions. We'll use radio buttons to select what conversion we're doing. Then we'll change to using a menu instead of the radio buttons.

Start a new application. Put two edit boxes on it. Rename the form conv2f1. Save the unit as conv2u1.pas. Save the project as conv2.dpr

Use the Object Inspector to make the initial values in the edit boxes 39.37 and 100.00 (37.39 inches is 100 centimeters.)

To begin with, we are merely going to get back to just about the place we were at the end of the first lesson about making converters. Thre will be slight changes, though, so work through the steps below. Just deal with any "I don't "get" that's" which arise along the way, because you should already understand everything we do down to where I've put in "New Stuff Begins Here."

Create a global variable called singTmp by putting...
  private

    singTmp:single;
... just after the ...
{ Private declarations }
... which you will find near the start of the unit's code. Global variables are to be used carefully. They can be a Bad Thing. But there's no harm in creating things like singTmp, as long as you use them for TeMPorary repositories for values. "Temporary repositories" in that you will put something in the variable, and use it from there shortly thereafter, but will not be interested in what was in the variable previously at any point further into the application.

Read thought the "But..." section below, then come back here and...

Make the first edit box's OnChange handler...
procedure Tconv2f1.Edit1Change(Sender: TObject);
begin
try
singTmp:=strtofloat(edit1.text)/0.3937;
edit2.text:=FloatToStrF(singTmp,ffFixed,7,2);
except
edit2.text:='';
end;
end;
...But! When you test the program: for the moment, change only what is in Edit1 and do not let that become nothing and do not put anything but digits into it.

Okay... do that, get that much working.

Now try adding a non-digit to what's in Edit1. You should get an error message. Remember that a user would not see this error message, and that you have to restart the program which was paused when Delphi saw you were trying the impossible: the conversion of a letter into a number.

Just to be sure all is well....
Save your application.
Use Delphi's File|Close All
Use Windows Explorer to run your .exe file

You should see sensible behavior, as long as you don't try to change what's in the second edit box... for now.

(It's not actually necessary to use Dlephi's Close All... but it is safer. That way you are certain that what you are seeing is coming from the application.

Re-Open your project.

At this point we need to re-vist something we touched on earlier: Begin... End blocks.

In an earlier application, we had...
if iCount=2 then memo1.lines.add('x x');
It simply said, "If such-andsuch is true, then do this".

In that case, there was just one thing to do.

In order to create a "If such-andsuch is true, then do this and this and this and this", we're going to "trick" Delphi. We're going to make several things look like "one" thing. All we have to do is put begin in front of the "this and this and this", and end at the end of it. We have created a block, and it ""boils down" to One Thing... the Thing to do if such and such is true. Before very long, you will be quite comfortably asking the computer to do several things when something is true. However, strictly speaking, you only ever do one thing because of any if... then... statement. The trick is in what you mean by "one thing". That thing can either be a simple statement, like ...
memo1.lines.add('* *');
.... or several things, "wrapped up" in a Begin... End, making them count, or boild down, to "one" "thing"....
if iCount=2 then BEGIN this; this; this END;
("this" is not real Delphi code; you wouldn't normally capitalize "begin" and "end")

Whew! What was all that about? Well....

Modify Edit1's OnChange handler as follows. Don't think about it too much just yet. But do notice the indenting and the commenting of the "end"s.
procedure Tconv2f1.Edit1Change(Sender: TObject);
begin
if edit1.text<>'' then begin
   try
     singTmp:=strtofloat(edit1.text)/0.3937;
     edit2.text:=FloatToStrF(singTmp,ffFixed,7,2);
   except
     edit2.text:='';
   end; //try
 end;// edit1.text<>''
end;// Edit1Change
Make an OnChange handler for Edit2, as follows. You can copy/paste from Edit1's OnChange handler, but be careful to change every 1 to a 2, 2 to a 1, and don't miss the need to change an / to a *!
procedure Tconv2f1.Edit2Change(Sender: TObject);
begin
if edit2.text<>'' then begin
   try
     singTmp:=strtofloat(edit2.text)*0.3937;
     edit1.text:=FloatToStrF(singTmp,ffFixed,7,2);
   except
     edit1.text:='';
   end; //try
 end;// edit2.text<>''
end;// Edit2Change
You should now have a converter which will convert from inches to centimeters, and from centimeters to inches. If you are running it from Delphi, non numeric input will give rise to the pain of acknowledging the error and restarting the program.

What was all that "begin.. end" fuss about? We had to add the "if edit(the other one).text <>'' then..." so that we didn't get one Onchange triggering the other box's OnChange. the glory of inter-actions between components is a sword with vry sharp edges... on both of it's two sides. Don't worry too much about what we did, how it works. There will be more chances to get to grips with such things. For those of you coming from more limited or more old fashioned programming environments: How would you have created something like our current converter in your old language? The clever thing is that you can change either the inches or the centimeters value, and the other one changes to be the equivalent of the one you changed. Pity the poor programmer: Users will never know just how clever that is. They will just assume that of course there's a way to make the application they are using easy to use.

Take a break! You've earned it!


I'm tired of that tedious business of having to acknowledge non numberic inputs. We're going to slightly hobble the program. We'll leave it slightly flawed, but much nicer to work with as we learn from things we can do to it. We'll learn a few things of general use along the way.

Click once on the edit box called Edit1 to select it. Press F11 to go to the Object Inspector. Click on the "Events" tab. Double click in the empty cell to the right of "OnKeyPress". (Not "OnKeyDown" or "OnKeyUp".... the differences are many, subtle, and a good source of headaches. To be honest, I would have given up trying to do what we are about to do if I hadn't by luck stumbled onto waht I wanted early on!)

When you double clicked in the empty cell to the right of "OnKeyPress", Delphi will have started off a skeletal OnKeyPress handler for you. Fill it in as follows. Don't worry that it is so weird... it is just a starting point.
procedure Tconv2f1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if key<>'5' then key:='5';
end;
Now run the program. Try to type things into Edit1. You should find that anything you type, apart from arrow keys and the delete key act as if you typed a 5! No more non-numeric inputs... even if only having 5 is "a little" limiting. note that while you can use the delete key, the backspace key no longer does anything.

Now revise the OnKeyPress handler...
procedure Tconv2f1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if key<>'5' then key:=chr(0);
end;
Now when you press non-5 keys, nothing happens. The 5 key still works!

In both cases, what was happening was that you modified the way Windows was behaving. Before we did things to the OnKeyPress handler, an OnKeyPress handler built into Windows did everything that was done between our pressing the key and what we pressed arriving into Edit1 's text property. We are able to "highjack" that process, and intercept the key on it's way, though Windows, from the actual keyboard switch to Edit1.text. We've taken advantage of our ability to to add code to that process, looking at what earlier parts of Windows have "harvested" from the keyboard, and modifying what gets passed to Edit1.text.

To make a more sensible handler, enter the following...
procedure Tconv2f1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if (key<>'0') and
   (key<>'1') and
   (key<>'2')
      then key:=chr(0);
end;
Now the program should accept only 0, 1, and 2.

A minor point... but you need to know.. when you have a couple of things after if, "glued" together with ands, you have to enclose each within brackets. Its another case of "boiling down"....
(key<>'0')
"boils down" to "true" or "false". When you ask if something..., the "something" has to be something which is true or false. "if the key is a 5...", "if I win the lottery...", "if the job is finished...", etc.

When you have a couple of things after if, "glued" together with ands, if you don't have the brackets in place, the ands do soemthing weird that you don't want me to go into here.

(You do want me to go into it? "if key<>'0' and key<>'1'... " starts by trying to and the '0' and the word key. You can and certain things, but not '0' and "key", which isn't what you wanted to do anyway.)

So... going back to the "boils down" point.... Seeing as...
(key<>'0')
"boils down" to "true" or "false", then...
(key<>'0') and
   (key<>'1') and
   (key<>'2')
"boils down" to something like "true and false and false", or "false and false and false", or "false and true and false". With and, if all the terms of the string (all the individual trues and falses) are true, then the whole thing "boils down" to "true".

Think about the following....
I promise that if...
-- You pay me enough, AND
-- It is Tuesday, AND
-- You supply the paint....
......then I will paint your fence.

What happens if it is Wednesday? What happens if the pay on offer is 5 cents? Our bit of Delphi code is really very like the simple English above.

An aside: You can join up things like (key<>'2') with ors, too. Getting your conditional test just the way you want it can be tedious! End of aside.

We could extend the above to cover 0...9, but it would be oh so dreary. Make the OnKeyPress handler...
procedure Tconv2f1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if not (key in ['0'..'9'])
      then key:=chr(0);
end;
Sure... its a little weird, and I doubt you would have stumbled upon it by yourself... but it works... mostly.

The limitations: You can't enter a decimal point. That's okay, you can work with the ONE that the output formatting gives you. If you COULD enter decimal points, you might ask the poor computer to try to turn 1.2.3 into a number.

You can't use the backspace key. that's okay... you can use the delete key. Remember: the point is to avoid those annoying error messages, not to have the perfect converting application.

But! While we've made Edit1 more resistant to silly input from users, Edit2 is just as vulnerable as it ever was. You're about to see something really cool....

Click on Edit2, then press F11. That will take you to the Object Inspector, with Edit2 the selected object for inspection (and modification). You'll probably even already be on the Events tab, with the OnKeyPress event selected. Get ther, if you aren't there already. Click on the down pointing triangle at the right hand end of the OnKeyPress line. You should see Edit1KeyPress on the drop-down list. Click on it. You've just told Delphi to use Edit1's OnKeyPress event handler whenever a key is pressed when Edit2 has focus. Run your program. Now neither edit box will accept anything except digits. You re-cycled the first edit box's OnKeyPress handler. Not only is this time saving, but it reduces the places available to errors for hiding.

Time for another break.


Now we're going to extend the application so that it can convert pounds to (and from) kilograms. I'm sure you remember that one kg is 2.2 lbs? If not, did you know that Google can help with such things? You enter something like "How many pounds is a kilogram?", and instead of being a search engine, and finding pages with "How", "Many", "Pounds", etc, it returns an answer to your question.

Our application will still be able to do it's inches to centimeters trick.

We need a way for the application to know which conversion it is supposed to be doing.

Add a global variable called iWhichConv. Do so by adding...
iWhichConv:integer;
... just after...
{ Private declarations }
... which is near the top of the unit's code.

Somewhere on the form, not on the edit box, not on the label, and not on the title bar, double click. The following should come up..
procedure Tconv2f1.FormCreate(Sender: TObject);
begin
end;
Insert the following between the begin and the end...
iWhichConv:=1;
Whenever iWhichConv is holding a 1, we want the converter to convert between inches and centimeters. If it holds a 2, we'll want it to converet between pounds and kilograms. We'll press on with making that happen...

Within Edit1Change, modify just one line, making it....
if iWhichConv=1 then singTmp:=strtofloat(edit1.text)/0.3937;
And, just after that, add...
if iWhichConv=2 then singTmp:=strtofloat(edit1.text)/2.2;
Before you can run the application, you need to make similar changes to Edit2....
if iWhichConv=1 then singTmp:=strtofloat(edit2.text)*0.3937;
if iWhichConv=2 then singTmp:=strtofloat(edit2.text)*2.2;
(Change 2 1's to 2's, and the / to a *)

Change the OnCreate handler so that the application begins with iWhichConv holding 2, and run your application. You should find that you have a pounds to kilograms converter.

Time to tidy things up. Add two labels. Re-name them laUnits1 and laUnits2. Put one above each of the edit boxes. Assuming you still have iWhichConv starting as 2, make the caption of laUnits1 "Pounds" and the caption of the other "Kilograms". Change the inital texts of Edit1 and Edit2 to 2.20 and 1.00.

Fine... as far as it goes. Add a button. Rename it buChange. (For "Change which conversion we want") Make it's OnClick handler what you see below. Remember the "boils down" idea. What you have below is basically one if.. then.. else. In working it up, I started with....
procedure Tconv2f1.buChangeClick(Sender: TObject);
begin
if iWhichConv=2 then begin
end // no ; here
else begin
end; // of else
end; // of buChangeClick
Note the comments to help me keep the different ends straight in my mind.

Only after I had a decent skeleton did I try to fill in the details. The program "ran" after the above... it just didn't run very differently!

Once you "flesh out" the "skeleton", as below, the converter should work well. Be sure you know why the different bits of buChangeClick are present.
procedure Tconv2f1.buChangeClick(Sender: TObject);
begin
if iWhichConv=2 then begin
   iWhichConv:=1;
   laUnits1.caption:='Inches';
   laUnits2.caption:='Centimeters';
   Edit1.text:='39.37';
   end // no ; here
else begin
   iWhichConv:=2;
   laUnits1.caption:='Pounds';
   laUnits2.caption:='Kilograms';
   Edit1.text:='2.20';
   end; // of else
end; // of buChangeClick
Notice that we only fill in Edit1.text? Edit2.text gets filled thanks to Edit1's OnChange handler which is triggered because we changed what was in Edit1.


We haven't used the term before, but the form is a container. The edit boxes, buttons and labels we've been putting "on" the form are contained by it. Just as you can put peaches in a bag, and put that bag of peaches in a bigger bag with your other groceries, sometimes you use containers in Delphi programming. We're now going to put some radio buttons into our converter application. They will be used to choose which sort of conversion we want to do. While we have only two conversions available, the button works, but we're likely to want to do more than just two sorts of conversion.

Delphi provides a RadioGroup component, but I think your purposes are better served at this stage by using a more general container, the GroupBox, and putting the RadioButtons into it by hand.

Put a GroupBox on the form. Put two RadioButtons on it. Name them rbInchCenti and rbPoundGram. Caption them "Inches / Centimeters" and "Pounds / Kilograms"

Re-caption the GroupBox "Choose conversion".

Change rbPoundGram's "checked" property to true. (This instruction could be given as "Make rbPoundGram.checked true".)

To your existing OnClick handler for buChange, just after...
iWhichConv:=1;
... add...
rbInchCenti.checked:=true;


To your existing OnClick handler for buChange, just after...
iWhichConv:=2;
... add...
rbPoundGram.checked:=true;
... and run the program. Clicking on the radio buttons will not yet have any significant effect, but, as ever, when you've made a change (making clicking buChange change the checked radio button) check that that much is working before going on.

As you will have probably guessed, double clicking on the PoundGram radio button will bring up a skeleton of the most commonly used event handler, which, of course, for the radio button is the OnClick handler. Fill it in as follows. (You only need to copy the heart of the "else" part of buChange's OnClick handler.)
procedure Tconv2f1.rbPoundGramClick(Sender: TObject);
begin
   iWhichConv:=2;
   rbPoundGram.checked:=true;
   laUnits1.caption:='Pounds';
   laUnits2.caption:='Kilograms';
   Edit1.text:='2.20';
end;
Double click on rbInchCenti. Fill in its OnClick handler as follows...
   iWhichConv:=1;
   rbInchCenti.checked:=true;
   laUnits1.caption:='Inches';
   laUnits2.caption:='Centimeters';
   Edit1.text:='39.37';
At this point, buChange has become redundant, and could be removed from your form.

An exercise you might want to complete would be to add a third conversion option, say miles / kilometers. You can do it! (But I'm not going to write out the details.)


The last thing we're going to use this exercise for is to demonstrate how a menu is added to an application.

Whenever we've added a component to date, we have been able to place it where we want it on the form, and, with certain limitations, re-size it to whatever shape suits our fancy.

When you add a menu component to your form, all you see on the form is a little square with a logo inside it. It doesn't matter where you put the square. Put it someplace out of the way. There are other components which do not give rise to obvious manifestations, as opposed to things like buttons which do result in something obvious in what the user sees.

Double click on the square, and a menu editor will arise. Click on the dotted rectangle in the upper left, Start typing "Choose Converstion" and you will find yourself in the Object Inspector entering a caption for a TMenuItem type object. When you finish "Choose Conversion", press enter. The menu editor will now be showing "Choose Conversion" with dotted rectangles below and to the right of it.

Click on the dotted rectangle to the right. Type "Quit". Press enter.

Click on "Choose Conversion", and the dotted rectangle below it re-appears. Click on that. Type "Pounds / Kilograms". Press Enter. You will now be in a new dotted rectangle below the previous. Type "Inches / Centimeters". Press enter.

Click the liitle "X", upper right, to close the menu editor. Save your project. Run it. You should find that you have a typical Windows menu, even if it has only two items, with two and no submenus, respectively. Clicking on the menu items doesn't do anything yet, but we've made a start!

Before you attach code to the differnt parts of the menu, get them sensibily named. At least add "mem" to the start of the names the computer gave the different entries in the menu. The reason you "need" to do this is that the object selction drop-down that is at the top of the Object Inspector list the objects of your application in alphabetical order. At the moment, the various partd of the menu are scattered all though the list in a dreadfully distracting way. Note that "Choose Conversion" is not an object, cannot be named. It just extablishes the submenu on which our two conversion types are offered.

A single click on the "Quit" in the menu now showing on the form will bring up a skeleton "OnClick" handler for the menu's Quit entry. You only need to put...
application.terminate;
...between the begin and the end

Save your project. Test that the "quit" in the menu works.

A single click on the "Pounds / Kilograms" in the menu will bring up a skeleton "OnClick" handler for that. Between the begin and the end, put the came code as you used for rbPoundGram...
iWhichConv:=2;
rbPoundGram.checked:=true;
laUnits1.caption:='Pounds';
laUnits2.caption:='Kilograms';
Edit1.text:='2.20';
A single click on the "Inches / Centimeters" in the menu will bring up a skeleton "OnClick" handler for that. Between the begin and the end, put the came code as you used for rbInchCenti...
iWhichConv:=1;
rbInchCenti.checked:=true;
laUnits1.caption:='Inches';
laUnits2.caption:='Centimeters';
Edit1.text:='39.37';
... and you should now be able to use the menu to select what sort of conversions you will be doing.

(The radio buttons now become superfluous, unless you want to give your users alternative ways of changing what type of converting we are doing.)

In the course of this application, we have entered two pieces of code, virtually unchanged, into the unit's code three times each. This is generally to be avoided. How we can avoid it is in a later lesson.

The reason you don't want a piece of code appearing more than once is that if you have to edit it, you have to remember (and succeed) to put the same changes in both copies of the code.


Who would have thought there were so many things to learn along the way of making such a trivial little application?




Click here if you're feeling kind! (Promotes my site via "Top100Borland")
   Search this site or the web        powered by FreeFind
 
  Site search Web search
Site Map    What's New    Search
Ad from page's editor: Yes.. I do enjoy compiling these things for you... I 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 Sheepdog Software (tm) freeware, shareware page.

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


Valid HTML 4.01 Transitional Page 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 .....