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

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

Page URL: Conv2.htm

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 Lazarus/ 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?)

This is just one exercise in a series of Lazarus / Delphi exercises. You will probably be best served by doing them in sequence... each assumes some prior knowledge. Material © TK Boyd, sheepdogsoftware.co.uk, 4/05-6/20.

Heavily re-worked, June 2020, to bring it up to date for Windows 10/ Lazarus 2.0.0 Based on earlier Delphi page. Probably still useful to Delphi programmers, too.




In the first lesson, we made a reasonable converter for finding out how many centimeters any number of inches equaled. 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

Pascal: the language behind both Lazarus and Delphi:
--- OnKeyPress event

Lazarus/ Delphi components:
--- Radio button
--- Menu
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 project as conv2.dpr. Save the unit as conv2u1.pas.

Use the Object Inspector to....
Name the edit boxes eInches and eCms, and... Make the initial values 39.37 and 100.00 respectively. (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. There will be slight changes, though, so work through the steps below. 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...
singTmp:single;
... just after the ...
private
... 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.

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.

Make the eInches's OnChange handler...
procedure Tconv2f1.eInchesChange(Sender: TObject);
begin
try
singTmp:=strtofloat(eInches.text)/0.3937;
eCms.text:=FloatToStrF(singTmp,ffFixed,7,2);
except
eCms.text:='';
end;
end;//eInchesChange


As before... if at any time during testing, you make either edit box empty, or enter something that isn't a number, you will get a Debugger Exception Notification. Just click "continue" to get past that.

You would NOT get those notifications if you were running the application directly from the .exe, rather than through the IDE.

------

Moving on... Make the change just noted, get that much working.

Try adding a non-digit to what's in Edit1. Make sure you get the warning you should. (You can run the .exe directly, too, if you wish. You don't even have to stop your Lazarus or Delphi session. Of course, if you do save your work, and try the .exe then, your test is just that little bit better: That way you are certain that what you are seeing is coming solely from the application.

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

----

At this point we need to revisit 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-and-such 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 boil 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 eInches'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.eInchesChange(Sender: TObject);
begin
if eInches.text<>'' then begin
   try
     singTmp:=strtofloat(eInches.text)/0.3937;
     eCms.text:=FloatToStrF(singTmp,ffFixed,7,2);
   except
     eCms.text:='';
   end; //try
 end;// eInches.text<>''
end;// eInchesChange
... and now give eCms an OnChange handler, as follows. You can copy/paste from eInches'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.eCmsChange(Sender: TObject);
begin
if eCms.text<>'' then begin
   try
     singTmp:=strtofloat(eCms.text)*0.3937;
     eInches.text:=FloatToStrF(singTmp,ffFixed,7,2);
   except
     eInches.text:='';
   end; //try
 end;// eCms.text<>''
end;// eCmsChange
You should now have a converter which will convert from inches to centimeters, and from centimeters to inches. If you are running it from the IDE, non-numeric input will give rise to the pain of acknowledging the error.

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. Look at the code. The glory of inter-actions between components is a sword with very sharp edges... on both of it's two sides. Try to get to where you see that would have been a problem, but 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 so easy to use.

Take a break! You've earned it!

Take a break!

I'm tired of that tedious business of having to acknowledge non-numeric 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.

Go the the form designer, and click once on the edit box called eInches 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 what I wanted early on!)

When you double clicked in the empty cell to the right of "OnKeyPress", the IDE 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.eInchesKeyPress(Sender: TObject; var Key: Char);
begin
if key<>'5' then key:='5';
end;
Now run the program. Try to type things into eInches. 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.eInchesKeyPress(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 when what we pressed arrived in eInches 's text property. We are able to "high jack" that process, and intercept the key on it's way, though Windows, from the actual keyboard switch to eInches.text. We've taken advantage of our ability to add code to that process, looking at what earlier parts of Windows have "harvested" from the keyboard, and modifying what gets passed to eInches.text.

To make a more sensible handler, enter the following...
procedure Tconv2f1.eInchesKeyPress(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 something 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.eInchesKeyPress(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 eInches more resistant to silly input from users, eCms is just as vulnerable as it ever was. You're about to see something really cool....

Get to the Object Inspector's view of eCms's events. Click on the down pointing triangle at the right hand end of the OnKeyPress line. You should see eInchesKeyPress on the drop-down list. Click on it. (And then leave the box to the right of the 'OnKeyDown' label, to make what you've done "stick". (Just pressing enter isn't sufficient in this case.) You've just told the IDE to use eInches's OnKeyPress event handler whenever a key is pressed when eCms has focus. Run your program. Now neither edit box will not accept anything but 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.

Remember: This "hobbling" was just to make our lives easier during the program's development. And it "costs" a "working" delete key. If you were writing this application as something to actually use, you might start with the KeyPress handlers, but, when things were nearly done, "disconnect" them.

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. Or just ask Alexa "Alexa: How many pounds are there in a kilogram?".

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...
singTmp:single;
... we put in at the start of all this, 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 convert between pounds and kilograms. We'll press on with making that happen...

Within eInchesChange, modify just one line, making it....
if iWhichConv=1 then singTmp:=strtofloat(eInches.text)/0.3937;
And, just after that, add...
if iWhichConv=2 then singTmp:=strtofloat(eInches.text)/2.2;
Before you can run the application, you need to make similar... but slightly different... changes to eCms....
if iWhichConv=1 then singTmp:=strtofloat(eCms.text)*0.3937;
if iWhichConv=2 then singTmp:=strtofloat(eCms.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 initial texts of eInches and eCms 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';
   eInches.text:='39.37';
   end // no ; here
else begin
   iWhichConv:=2;
   laUnits1.caption:='Pounds';
   laUnits2.caption:='Kilograms';
   eInches.text:='2.20';
   end; // of else
end; // of buChangeClick
Notice that we only fill in eInches.text? eCms.text gets filled thanks to eInches's OnChange handler which is triggered because we changed what was in eInches.


Two final bonuses!

If you don't want to go on, you can skip this final bonus. There's nothing of the "Oh, and you should be aware..." sort.

But if you quit now, you'll miss two useful things that won't be hard to grasp: Radio buttons for making choices, and menus. Both are widely used in serious applications because they are good at what they are for!

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 containers in Pascal programming. (Lazarus and Delphi both use Pascal... they become Lazarus and Delphi when you also consider the IDE.

We're now going to use some radio buttons in our converter application. They will be used to choose which sort of conversion we want to do. As long as we only offer two conversions, buChange will do... but that approach limits us. It would be far better is to use...

Radio buttons!

Lazarus/ 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".

Put "rbPoundGram.checked:=true;" in the FormCreate handler change, just under "iWhichConv:=1;".

(The initial state of the radio buttons can also be changed in the Object inspector, but it would be safer to do it as indicated, because if you change either the initial value of bWhichConv or the radio buttons, you have to change the other. What's in the FormCreate handler ill over-ride anything specified by the Object Inspector's settings. Rems regarding the fact that iWhichConv and which radio button is checked having to stay "in step" on those lines in FormCreate would be a very good idea. I've spent about 40 minutes getting all of this "just right" in a session of merely editing this text, because I kept getting things out of step.)

To your existing OnClick handler for buChange, replace the...
iWhichConv:=1;
... with...
  iWhichConv:=2;//If this is 2, rbInchCenti.checked should be true
       //If it is 1, rbPoundGram.checked should be true
   rbInchCenti.checked:=true;//See rems above, on iWhichConv...


Revise your existing OnClick handler for buChange to...
procedure Tconv2f1.buChangeClick(Sender: TObject);
begin
if iWhichConv=2 then begin
   iWhichConv:=1;
   rbInchCenti.checked:=true;
   laUnits1.caption:='Inches';
   laUnits2.caption:='Centimeters';
   eInches.text:='39.37';
   end // no ; here
else begin
   iWhichConv:=2;
   rbPoundGram.checked:=true;
   laUnits1.caption:='Pounds';
   laUnits2.caption:='Kilograms';
   eInches.text:='2.20';
   end; // of else
end; // of buChangeClick 
(That only involves adding the two "...checked:=true;" buttons. (When you make a RadioButton "checked", all other radio buttons in the same group become un-checked. It's in the nature of radio buttons. They are named for something that died out around 1980.

... 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 at least not making problems before going on.

General point: Often, double-clicking on a component in the form designer will bring up a shell for the most commonly tweaked event handler.

Don't rely on that shortcut for what we are about to do. We want alter the radio button's Click handlers, not their Change handlers.

Go to the Object Inspector. Get to the Events tab for rbInchCenti. Double-click on the so-far empty box to the right of "OnClick" to create a skeleton for that. Fill it in as follows.
procedure Tconv2f1.rbInchCentiClick(Sender: TObject);
begin
   rbInchCenti.checked:=true;
   laUnits1.caption:='Inches';
   laUnits2.caption:='Centimeters';
   eInches.text:='39.37';
end;//rbInchCentiClick
Make rbPoundGram's OnClick handler be...
procedure Tconv2f1.rbPoundGramClick(Sender: TObject);
begin
   rbPoundGram.checked:=true;
   laUnits1.caption:='Pounds';
   laUnits2.caption:='Kilograms';
   eInches.text:='2.20';
end;//rbPoundGramClick
At this point, buChange has become redundant, and could be removed from your form, along with the code (in the source editor) for responding to it.

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.)


Menus!

As you've worked so hard, you get one last treat... menus!

Here's 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.

You want the TMainMenu sort of menu. In the version of Lazarus I'm using in June 2020, that's the first component on the "Standard components" tab. Put one on the form, the way you would put a button on the form... but...

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 open...
-

(Confession: It is late. I am hungry. I thought the edit I've nearly finished would take about 30 minutes... it has taken 3 hours (so far). I'm going to leave it to you to CHANGE what you see in the editor to...)
-

(I did find it a bit "fiddly" I'm sure You Can Do It!! *All* that's different is that where the editor used to have "MenuItem1" we now see "ange Conver" which is part of "Change Conversion". When you've managed that, when you run the app, you will see...)
-

See the "Change Conversion" near the upper left? That's new! That's the first item for our menu!

Run your project. You should have the "one item menu".

Fiddle with the menu editor some more, adding two "sub-menus" to the main menu item we already have. That will lead to...
-

Run your project again. The menu... and sub-menu items still don't DO anything, but we're making progress!!

In the Form Designer window, you should see "Change Conversion" in place, upper left, where you would expect a menu item to be.

Click on it. The sub-menu items appear. (Or at least they should!)

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';
eInches.text:='39.37';


In the same way, make an "OnClick" handler for the other sub-menu. 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';
eInches.text:='2.20';


Run the program.

... and you should now be able to use the menu to set the applications to the sort of conversions you want!

(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?



Search just this site without using forms,
Or... again to search just this site, use...

Powered by FreeFind

Site search Web search

The search engine merely looks for the words you type, so....
  *!  Spell them properly   !*
  Don't bother with "How do I get rich?" That will merely return pages with "how", "do", "I", "get" and "rich".



I have other sites...
   SheepdogSoftware site.
   My site at Arunet.


Ad from page's editor: Yes.. I do enjoy compiling these things for you... I hope they are helpful. However.. they don'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 freeware, shareware page.
Link to Tutorials main page Link to Lazarus/ Delphi Course index

To email this page's editor, Tom Boyd.... Editor's email address. Suggestions welcomed! Please cite "Conv2.htm".

Click for W3.org HTML validity test Page has been tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org. Mostly passes.

AND passes... Click to check CSS validity


One final suggestion: Be sure you know all you need to about spyware.

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