HOME - - - - - Lazarus Tutorials TOC - - - - - - Other material for programmers
Delicious.Com  Bookmark this on Delicious     StumbleUpon.Com Recommend to StumbleUpon

Gently Does It

Learning to Program with Lazarus... first steps

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!

This page is "browser friendly". Make your browser window as wide as you want it. The text will flow nicely for you. It is easier to read in a narrow window. With most browsers, pressing plus, minus or zero while the control key (ctrl) is held down will change the texts size. (Enlarge, reduce, restore to default, respectively.) (This is more fully explained, and there's another tip, at my Power Browsing page.)


We're going to create an application which will put an arithmetic question on the screen. It won't complain if your answer isn't correct, it will just wait for you to make it right. It will tell you you've (finally) got it right, and then give you (or your child!) another, and another, and another...

Users of the application will see...

Image of running application

Along the way, we are going to use variables, use an edit box, write an event handler for the edit box's OnChange event, and use the built in random() function (and it's relative randomize.)

We're going to write the application really badly at first, the way a beginner would write it. We'll look at what is so terrible about the "bad" version, and then see how to create a user created procedure to take the badness away.

At the end, we'll add a little frill, to "exercise" some of the programming knowledge acquired along the way.

Is nothing simple?

Wow! I was trying to come up with a "little" application, which would illustrate just one or two things. Don't worry, none of the above is rocket science, and all of them are fundamental to Lazarus (and Delphi) programming, so while this tutorial may take a while to work through, by the end of it any beginners should have made huge strides towards Lazarus fluency.

For almost everything in this tutorial (the main exeption has an explanatory note at the relevant spot), what you would do in Delphi would be the same. At this level, the tutorial is as good for Delphi learners as for Lazarus learners. (I actually "did" the tutorial, but under Delphi 4, to test that assertion!)


Creating the application...

Create a new application...

If you can't easily complete what is sketched in that list, may I please encourage you to run through my introduction to Lazarus programming tutorial? It will show you how, and introduce you to some other things I just presume you know.

Then use the component palette....

Lazarus components palette

...to put a label on your form at the left hand side of the form, just below the "Quit" button already on the form. (The label component is the 5th one, the one with "Abc" on the icon.) Make the label's name property to "laQuestion" with the Object Inspector.

Now add an edit box to the form. The edit box is the sixth component on the "standard components" tab of the component palette, the one with "ab" and the insertion point in a little box, the one with "TEdit" for its pop-up hint. Put it just below the label you created a moment ago. Make the edit box's name property to "eAnswer".

That's it for the components our form needs

Variables

Forgive a little nostalgic ramble? I can recall in remarkable detail the first lesson I had in programming. Per Ranhoff, a monumental mathematics teacher, not to mention someone who as a boy helped Allied fliers escape Nazi occupied Europe, had somehow, in addition to his normal workload, learned enough to show us how to use a PDP-8 computer. And in that first lesson he introduced us to variables.

A variable, as Per put it, is like a post office box. It is a place where you can put something. You can look at what is there. You can change what is there. Well... you can do these things once you know how the computer language you are working with creates and uses variables.

Note that I said creates and uses. There are two things to master, before you will have variables in your repetoire.

Creating variables: We're now going to create two variables for our application.

A moment ago, part of the code for our application looked almost like...

  TLD002f1 = class(TForm)
    buQuit: TButton;
    eAnswer: TEdit;
    laQuestion: TLabel;
  private
    { private declarations }
    bNum0,bNum1:byte;
  public
    { public declarations }
  end;

I said "almost", because a moment ago, it didn't have the...

bNum0,bNum1:byte;

... line. Add it to what is on your screen.

That will create two variable: bNum0 and bNum1

Some ideas to take on board...

* Lazarus may refer to what you have created as "private fields". (They are private fields! But the alternate term "variable" is not so terrible as to make me eschew it.)

* Lazarus will refer to those private fields by their full names, e.g.: "LD002f1.bNum0". (That hierarchical "dot" naming is tremendously powerful. Watch out for it, as you progress, but don't worry about it just now.

* There are many types of variables, and when a programmers write about "types", they are using the term in a geeky and specific sense. The type of a variable... "byte" in the case we've just met... determines the sort of thing which can and cannot be stored in the variable. In the case of a byte-type variable, we can store whole numbers only, and only numbers in the range 0-255, inclusive. We will meet another type shortly.

* When we added "bNum0,bNum1:byte;" to our application, we were "declaring" the variables. That is the name for doing what it takes to make them available for the application's code to use. The declaration is all about telling your compiler to set aside some memory for storing things... but don't worry about the details. You are creating the "post office boxes" I spoke of earlier. Do not assume that you know what is in those boxes at the beginning, by the way. Always, somewhere, before you need to use the contents of a "box" (variable), put something in it "by hand". Again, you will see that done in this tutorial.

* Use logical names for variable. I could have called my variables "Peg" and "Fred"... the application would still run. But logical names are best. I always start the name of a byte-type variable with a "b". The next character is always an upper case letter. When I need two variables for similar tasks, the names are similar, with a digit added to the end. Computer programmers count from zero, hence the names bNum0, bNum1 as variables for holding some numbers in. The numbers we want added together by the application's user will go in those variables.

WHEW!

Something you don't need to worry about for a while: Why did I put my new line in the "private" section, rather than the "public" section? That will only matter when you start creating applications with more than one unit... something that you don't need to do for quite a while yet, if you are a beginner.

The form's OnCreate handler

Put your mouse pointer over some part of the form that doesn't have anything on it (apart from the grid of dots), and double-click. The following new text should appear in the Source Editor...

procedure TLD002f1.FormCreate(Sender: TObject);
begin

end;

Between the begin and the end, insert...

laQuestion.caption:='Hi';

... and run the application. The form should appear, and in place of laQuestion you should find "Hi".

What is going on?

The label component we created and named laQuestion has a caption property. When we named the component "laQuestion", Lazarus changed the initial caption to "laQuestion" too. This is an example of setting a property at design time. I prefer to use the component's name for it's initial caption, because then, while doing the programming, I can easily see what the component's name is merely buy looking at it on the form.

When the application starts up, one of the first things it does is to put your form on the user's screen. But just before it appears, if there is some code in a....

procedure TLD002f1.FormCreate(Sender: TObject);
begin

end;

... block, then whatever is there is done. And, as you have seen, we can, for instance, change what is in the caption field of one of the components on the form before the form (and its constituent components) appears.

By the way... do not try to create the text "procedure TLD002f1.FormCreate..." by typing it in by hand. When you caused Lazarus to create that text, it did some other things behind the scenes.

The creation of the form is an event. Lazarus will do certain things behind the scenes whenever a form is created. If you want to add to what happens at that time, you do it with code in the block we have been discussing, which is the form's "OnCreate event handler".

Changing the caption of laQuestion was a trivial thing. Let's do something worthwhile. Alter the code in the event handler. Change it to....

procedure TLD002f1.FormCreate(Sender: TObject);
begin
  bNum0:=5;
  bNum1:=2;
  laQuestion.caption:='What is '+inttostr(bNum0)+
      ' + '+inttostr(bNum1)+'?';
end;

Ah ha! I told you that variables should always be initialized, and here you see me initializing bNum0 and bNum1.

It's a pity that... for now... the question will always be "What is 5+2?". We'll fix that. Later. After you've had your vegetables. Sorry.

Don't miss a pesky little detail in the example above: When we want to put something in a variable (or change what is in a property), we use two characters to say "becomes". The characters are the colon and the equals sign... but don't think of it as "equals". Read the two together as "becomes". So, we have, above, "bNum0 becomes 5".

Moving forward: A short time ago, we had a nice simple line...

laQuestion.caption:='Hi';

That "said": "The caption property of the component (or object) laQuestion becomes 'Hi'".

The caption property can only strings, i.e. it can only hold things of the "string" data type.

The great long thing we created....

'What is '+inttostr(bNum0)+
      ' + '+inttostr(bNum1)+'?'

... "boils down" to a simple string.

Boils down to a string?

First of all, anything between single quote marks, e.g. 'Hi', is a string.

You can "glue" two strings together into a bigger string (the grown-up term is "concatenate") with the plus sign. So...

'Hi'+'Earthling'

... is equivalent to...

'HiEarthling'

(If you want 'Hi Earthling', be sure to add the space either after the Hi or before the Earthling... or I suppose you could do...

'Hi'+' '+'Earthling'

.. too!)

A digression for a detail...

If you have a long line of code, like our laQuestion.caption... line, and want to split the "logical" line across several lines on the screen, you can, in general. If you have a logical line with a bit of string data, though, don't split the line in mid string. Split it just after one of the + signs, as I have done in the example. You can even...

sTmp:='Split a very long string into shorter ones.';
sTmp:='Split a very long '+
  'string into '+
  'shorter ones.'

... with single quotes and plus signs.

Back to work...

bNum0 is a byte-type variable. The caption property can only hold string-type data. You can't say....

laQuestion.caption:=bNum0;

(Try that, to see what happens. You get a compile time error"... which is good! The application doesn't even start to run, because there is something "impossible" in your code. Better that the application doesn't run, than start, and then fall over. Once you have all of the compile time errors out of some code, and you can run it, you then move on to the phase in which you iron out the "run time" errors. In the context of the application we are building, it would be quite easy to build something that asked users "What is 5+2", but treated "10" (5 times 2) as the "right" answer. That would be a run time error. Sort of. It would, at least, be an error that you only encountered when the application was running. A more narrow definition of a run time error would be something that causes the application to "die", not merely not do what you wanted.)

ANYWAY... you can't say...

laQuestion.caption:=bNum0;

... because that's an attempt to put some byte-type data into something that can only hold string-type data.

Of course, the "answer" is easy. The following, after you have initialize bNum0, is perfectly legal...

laQuestion.caption:=IntToStr(bNum0);

... but, as you may have guessed, I will have to talk about it, even though it seems "obvious" what is going on. It is, if I may say so, "obvious" because of how you have been led gently to it. And if you take a minute to consider some points, the next thing I'm leading you to will be obvious, too.

IntToStr() is a function. It "boils something down". In the case of....

IntToStr(bNum0)

The IntToStr function "boils down" what's inside the brackets, i.e. the ( ), into a string. We put the variable bNum0 inside the brackets. The computer "looked" inside the "post office box", found the number we had put there. It then changed the number into a string. The five we put into the variable, and the "5" which appeared on the screen might appear to be just a five, to you and me. But to the computer, 5-the-number, and 5-the-string are different, and we have to work with this reality.

Part of learning to work with any language is to learn about all of the marvelous functions which have been provided for you by the creators of the language. And you will also be creating your own functions... quite soon!

A function is given some "ingredients" and it returns a "result". The result is whatever you get when you apply some rule to the ingredients. In the case of IntToStr, the function takes a number and returns a string. The name comes from "(convert an) INTeger TO a STRing".

Just before we move on, look at our code again....

laQuestion.caption:='What is '+inttostr(bNum0)+
      ' + '+inttostr(bNum1)+'?';

You should now see that it more or less says...

laQuestion.caption:='What is '+'5'+
      ' added to '+'2'+'?';

We're just concatenating a bunch of short strings... but there's a little magic in our actual code... what goes into the caption property in the location of the 5 and the 2 depends upon what is in bNum0 and bNum1

But what's the answer??

Take a break. You've earned it. If you've really worked at the above, the next bit will be quite easy.

We're going to create another variable, calling it sAnswer. This will be string-type data. And we will fill it with the answer to the question the application is asking the user. (It is slightly ironic, but perfectly acceptable, that we don't store the answer to the question, as a number, anywhere in the program! But we do hold the string that the answer turns into if you change the answer (a number) into a piece of string-type-data.)

Up at the top, where we have...

  private
    { private declarations }
    bNum0,bNum1:byte;

... add a line, making it...

  private
    { private declarations }
    bNum0,bNum1:byte;
    sAnswer:string;

And now, down in the FormCreate procedure, just before it's "end", add....

sAnswer:=inttostr(bNum0+bNum1);

If bNum0 holds 5, and bNum1 holds 2, as they do, at the moment, then after the line above, the string sAnswer will hold 7... as a string, not as a number.

That wasn't hard!

Now we will turn to how to get users to "tell" the computer they thing 5+2 comes to. And check the user's answer against what is in sAnswer.

Number Five needs input, Stephanie!!

Bear with me a little... we have to go out on some thin ice, but if you keep your wits about you, I'll keep you safe until the ice grows thicker, as you begin to understand how everything works.

Way back when we started, you put an edit box on the form.

If you run the application, you will find that you can already type things into the edit box.

Add a line to the form's OnCreate handler saying...

eAnswer.text:='';

(That's two single quotes, not one double quote.)

That line says "make the eAnswer component's text property hold nothing, make it empty". The property called "text" is an edit box's version of the property called "caption" in a label.

Stop the application, if you have it running. Go back into the design state. Double click on the edit box, and you should get the following in your Source Editor...

procedure TLD002f1.eAnswerChange(Sender: TObject);
begin

end;

The double click has told Lazarus that you want to create a handler for the most commonly used event of the component. I've put "event" in bold italics, because events are crucial to Windows and Linux programming. We'll talk about it later, for now we will just use it.

Change the above to....

procedure TLD002f1.eAnswerChange(Sender: TObject);
begin
if eAnswer.text=sAnswer then begin
   showmessage('Right!');
   end;
end;

Run the application, play with it a bit. Each time the contents of eAnswer is changed to 7, the answer to our question, the "Right" message pops up.

Wow!

Getting so much better all the time

So far, so good.

To "take it to the next level", you need to know about the built in random() function.

Replace the line in the FormCreate handler which says...

bNum0:=5;

... with...

bNum0:=random(10);

Just before it, add a line that says...

randomize;

The first line, "bNum0:=random(10);", will cause a number to be put into bNum0... but you won't know what the number will be! But it will be an integer, in the range, 0-9 (nine) inclusive. Strictly speaking, it won't be a "random" number... but for many, many purposes, it will be as good as random.

The other line we added, "randomize;", is connected with my qualification above, about the number not being truly random.

Without the randomize, which you only need to put in the application's code once, before the first call of random() you would get the same sequence of "random" numbers each time you ran the application! Getting the same sequence can be helpful, if you are debugging something. To get the same sequence each time, just take out the randomize statement. Different sequences are usually best in finished applications. Just put the randomize back!

Play with that a bit. At some point change the bNum1:=2; to bNum1:=random(10);

Fine. As long as you don't mind restarting the application again and again, to get different questions. Have a little think... can you see how to get a different question each time you answer the previous question correctly? Don't scroll down until you tried to think of answers for yourself... the answer is just below here...

Getting different questions

Look again at the handler for the OnChange handler of the edit box, eAnswer. That code executes every time what's in the edit box changes, which, in this application, happens when a user enters something on the keyboard, including the "delete" or "backspace" keys, etc.

procedure TLD002f1.eAnswerChange(Sender: TObject);
begin
if eAnswer.text=sAnswer then begin
   showmessage('Right!');
   end;
end;

You will gradually learn to see Pascal code in "blocks". What we have above, at the highest level, is...

procedure TLD002f1.eAnswerChange(Sender: TObject);
begin

   (SOMETHING)

end;

The"something" is....

if eAnswer.text=sAnswer then begin
   showmessage('Right!');
   end;

That something can be seen as....

if eAnswer.text=sAnswer then begin

     (ANOTHER THING)

   end;

... with "another thing" being

showmessage('Right!');

With a s mall snippet of code like that, the above analysis is a long way "over the top", but try to grasp the concept. An application with 1500 lines of code can only be understood if it is looked at by those techniques.

The begins and ends in the above are important to the structure of the code. I can't "tell you all about" begins and ends.. the concepts behind them will be something you gradually master as you work through programming tasks.

For now, look closely at....

if eAnswer.text=sAnswer then begin

The "then" in that has nothing to do with time. It is all about consequences, e.g. "If you play with fire, THEN you will get burned."

And the handler for the OnChange event is where we need to add some code, if we want our application to behave sensibly. Make the handler's code....

procedure TLD002f1.eAnswerChange(Sender: TObject);
begin
if eAnswer.text=sAnswer then begin
   showmessage('Right!');
   bNum0:=random(10);
   bNum1:=random(10);
   laQuestion.caption:='What is '+inttostr(bNum0)+
      ' + '+inttostr(bNum1)+'?';
   sAnswer:=inttostr(bNum0+bNum1);
   eAnswer.text:='';
   end;
end;

Notice that the "new" stuff is all inside the begin / end block which is executed when eAnswer.text does equal sAnswer. I.e. what is in the text property of the eAnswer edit box (which users can change, just by typing) is the same as (equals) what you stored in the variable sAnswer when you picked new numbers for the question.

Re-read that last paragraph. What it says is intuitively obvious, I hope. I worked quite hard on it to use terms well, so that you will be able to understand what I say later, when I am explaining something that is not so obvious. Master the jargon.

So... we have something that "works". Good? Not good. It is badly written. And badly written applications always "fall over" eventually, they are hard to modify, etc, etc, etc. They will bring the bedbugs of a thousand flop houses to your home.

What's so terrible about that code? We've written out a bunch of stuff twice...

bNum0:=random(10);
   bNum1:=2;
   laQuestion.caption:='What is '+inttostr(bNum0)+
      ' + '+inttostr(bNum1)+'?';
   sAnswer:=inttostr(bNum0+bNum1);

You should always be nervous if you write something twice. What happens if you need to make a change. Suppose, for instance, you want to modify the program so that it asks multiplication questions instead of the addition questions it asks now? (The sign for "multiply" is *, by the way. You might like to try making the change, either now or later. If you've really understood what is in this tutorial, the changing isn't hard at this stage, and will be really easy by the time we've "fixed" the Bad Programming.)

Recap of functions

Previously, we've seen the random() function. We've seen the IntToStr() function. Each "returns" something to you, something based on what you sent to the function inside the brackets.

You've also seen procedures, although I haven't used the term before now. You've seen showmessage(), you've seen randomize

A procedure does something for you. It doesn't "return a result", the way a function does. You might think that showmessage() returns a result, because it puts your message on the screen, but that sort of thing doesn't count. You are only returning a result if the function call "boils down" to a new datum, something that can go into a variable, for instance. Or into a property of some component.

Not only does Lazarus come with a bunch of procedures (and functions), but you can make your own. (Procedures, or functions.) We're going to use that capability now.

Roll your own

Up near the top of the program, we declared 3 variables...

  private
    { private declarations }
    bNum0,bNum1:byte;
    sAnswer:string;

Make that...

  private
    { private declarations }
    bNum0,bNum1:byte;
    sAnswer:string;
    procedure PrepareQuestion;

And just before the "end." at the end of the code... ("end." with a full stop, period, notice. Not a semicolon for once)... add...

procedure TLD002f1.PrepareQuestion;
begin
   bNum0:=random(10);
   bNum1:=2;
   laQuestion.caption:='What is '+inttostr(bNum0)+
      ' + '+inttostr(bNum1)+'?';
   sAnswer:=inttostr(bNum0+bNum1);
   eAnswer.text:='';
end;

(I'll go through that in a moment.)

Look familiar?

Now, in the two old places where you had...

   bNum0:=random(10);
   bNum1:=2;
   laQuestion.caption:='What is '+inttostr(bNum0)+
      ' + '+inttostr(bNum1)+'?';
   sAnswer:=inttostr(bNum0+bNum1);
   eAnswer.text:='';

... replace just that much, twice, with...

PrepareQuestion;

That, for my Lazarus readers, will make the whole thing what you see below. There are a few differences in the first few lines for my Delphi readers... but those lines were created by Lazarus or Delphi, and I haven't told you to tinker with them, have I?

(The top of the Delphi would look like...

unit LD002u1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls;

type
  TLD002f1 = class(TForm)

The Lazarus code is...

unit ld002u1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics,
  Dialogs, StdCtrls;

type

  { TLD002f1 }

  TLD002f1 = class(TForm)
    buQuit: TButton;
    eAnswer: TEdit;
    laQuestion: TLabel;
    procedure buQuitClick(Sender: TObject);
    procedure eAnswerChange(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { private declarations }
    bNum0,bNum1:byte;
    sAnswer:string;
    procedure PrepareQuestion;
  public
    { public declarations }
  end;

var
  LD002f1: TLD002f1;

implementation

{$R *.lfm}

{ TLD002f1 }

procedure TLD002f1.buQuitClick(Sender: TObject);
begin
  close;
end;

procedure TLD002f1.eAnswerChange(Sender: TObject);
begin
if eAnswer.text=sAnswer then begin
   showmessage('Right!');
   PrepareQuestion;
   end;
end;

procedure TLD002f1.FormCreate(Sender: TObject);
begin
  randomize;
  PrepareQuestion;
end;

procedure TLD002f1.PrepareQuestion;
begin
   bNum0:=random(10);
   bNum1:=2;
   laQuestion.caption:='What is '+inttostr(bNum0)+
      ' + '+inttostr(bNum1)+'?';
   sAnswer:=inttostr(bNum0+bNum1);
   eAnswer.text:='';
end;

end.

Cool? If you promise to come back and finish the tutorial, you can take a break by trying to change the application so that it will give multiplication questions

Back to work

We've just created our own procedure. I want to tell you some of the things that have to be done right:

The procedure name should be put together like a variable name: Start with a letter, use only letters and digits. Make it describe what the procedure does.

A little "gotcha": Don't use a name that Lazarus already uses, like ShowMessage. But it's a gotcha, 'cause you don't know all the procedures, do you? And the "worse news"? Lazarus won't complain if you do "recycle" one of the names it knows... it will just act weird if you try to use the built-in procedure and your procedure of the same name.

A detail: in the "private" block, where you declare your variables and procedures (and, in due course, user-created functions), do all of the variable declarations before you do any procedure or function declarations. (The latter two can be all jumbled up, though.)

That's (almost) it.

You can stop reading here.

It really would be best if you were to build the application by working through the text above, but if you have tried, and it Just Won't Work, and you are wondering if your Lazarus isn't installed properly, you can download a zip file with everything in it. Just unzip the contents of the file to a folder, any (empty) folder, double-click on the .lpi file, and you should have everything you need. (On a machine with no Lazarus installation, you can still use the LD002.exe file.)

For the keen...

Just for the exercise, I'm going to extend the application just a little bit. I want to display how many times eAnswerChange is called, in hopes of impressing you with...

Put a new label on the form. Call it laCalls

In the FormCreate procedure, add...

laCalls.caption:='';

Declare a new variable. The following should go just after the "sAnswer:string;" we already have

wCalls:word;

(A "word" type variable can hold integers in the range 0-65535, inclusive)

Add "wCalls:=0;" to the FormCreate procedure.

In the eEdit OnChange handler, make the changes you see in this complete listing of the new handler...

procedure TLD002f1.eAnswerChange(Sender: TObject);
begin
wCalls:=wCalls+1;
laCalls.caption:=inttostr(wCalls);
  if eAnswer.text=sAnswer then begin
   showmessage('Right!');
   PrepareQuestion;
   end;
end;

That's it!

Just notice that the new stuff is before the if... then... part. It will happen every time the eAnswerChange procedure executes.

One little thing which you should have noticed, may wonder about... What on earth is...

wCalls:=wCalls+1;

... all about? Your algebra teacher would not be impressed!

But then again, he doesn't know to read it as "wCalls becomes wCalls plus one".

The = sign is merely part of the two character "word" ":=". We are NOT saying "wCalls EQUALS wCalls+1".

What we are saying is....

"In the wCalls variable after this line is finished, we want what was in it before, with one added. I.e. "wCalls BECOMES the old contents of wCalls, plus one."

Congratulations!

I've spend a good chunk of two days typing the first draft of this tutorial and the one for LD001. If you are new to programming, you have worked very hard to get this far. But, and I really mean this, if you have really concentrated, not gone "yeah, yeah" too often, then the back of "becoming a programmer" is broken. Yes, you do still have things to learn. But things will move much more quickly from here.





            powered by FreeFind
  Site search Web search
Site Map    What's New    Search This search merely looks for the words you enter. It won't answer "Where can I download InpOut32?"

If you visit 1&1's site from here, it helps me. They host my website, and I wouldn't put this link up for them if I wasn't happy with their service. They offer things for the beginner and the corporation.www.1and1.com icon

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!!! Sheepdog Software (tm) is supposed to help do that, so if you found this stuff useful, (and you run a Windows or MS-DOS 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 Lazarus Tutorials main page
How to contact the editor of this page, 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. Mostly passes. There were two "unknown attributes" in Google+ button code. Sigh.


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