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

Delphi: Program for data entry.. an example of things.

This tutorial is not as polished as most, but there is good information here. You can download a zip file with the sourcecode. The zip archive also includes the finished application... have a little play with it to fully understand most easily what it does.

It is highly unlikely that the application produced in this tutorial is going to be of any use to you. The tutorial is written with hopes that you will still learn a lot from the techniques applied to the problem solved, techniques that will help you create the things you DO need.

I wanted to create text files like....

tkf0101a001a
tkf0101a002e
tkf0101a003a
tkf0101a004a
tkf0101a005d
tkf0101a006e

This was in connection with a program that helps me learn the answers to multiple choice questions in a thick book. The text file has embedded in it the right answers to every question in the book. I look at the book for the question, and give the answer to the computer, which scores my work, and decides what question I should answer next... based on my previous history of successes and failures with the individual questions in the book.

Before we go further: Two terms from database work: RECORD and FIELD.

Each full line in the textfile is one RECORD. It is all the computer knows about one of the questions.

Each record is made up of a fixed number of FIELDS, There are six fields in the data we're discussing here.

Fields come in two flavors- fixed length and variable length. (In the project this tutorial discusses, all the fields are fixed length.

Fixed length fields are nice for computers and programmers, but sometimes inconvenient for humans. Fixed length fields always have the same number of characters in them.

What "varies" in a variable length field, as I hope you guessed, is the number of characters making up the field. A database with variable length fields needs some mechanism to tell it where one field ends and the next begins. Happily, that's not something we need to go into today.

If you are skimming this, you can skip past the details of the data... each line will have 6 fixed length fields. The operator needs to enter the 6th by hand for each record, and the 5th will normally go 1,2,3..., and the others change more slowly.

Here's how the data in the specific case we are going into today is coded.... (By the way: thank you for not skimming!)

The first two characters of the line hold what I need for the first field. They define who is in charge of the rest of the codes in the record. Typically... but not necessarily... all of the records in one file would be managed by one editor. Thus many editors, each with their own "editor code", can create data files. The way one editor assigns codes for the remaining fields will not conflict with how someone else assigns the codes. For example, in the next field, Editor A could use f01 for a book about birds, and Editor B could use f01.. the same code... for a book about geography... without any confusion arising.

As you've probably inferred, the next three characters are for the editor to assign to say which book the question comes from.

Usually, a data set will only relate to one book. There may even be books requiring multiple data sets. Thus, one data set will probably have the same first five characters in all lines of the file, but if the editor makes a second file, for the questions in another book, there will be a change in the second field, i.e. characters 3,4, and 5, if you count them as humans do (first character called "character 1") and not as computers do (first character called "character 0").

Moving on...

The third field is held in characters 6 and 7. It is for the chapter a question comes from.

The fourth field is held in a single character. It is for the sub-section of the chapter the question comes from.

The fifth field is the question number. Even this is a fixed length field because it makes other things simpler. The number of characters is always 3. This allows for....

001
01A
01B
020
021
21B
100
999

Not allowed: 5, 66, 1000, 100B, etc.

And the last field is the answer, which will always be a,b,c,d or e.

That's the overview. There are places where variations can be accommodated, and details which I haven't yet expounded, but we have enough to be going on with.

===

The program will present the user with a window with edit boxes for what should be in each of the fields.

When the user types the answer to a question... from the list of right answers at the back of the book...

... and the program waits for the user's next move. Either the user will type in another answer, in which case the above repeats, or the user will make changes to one or more of the more slowly changing fields, in which case the program will wait for the next new ANSWER before delivering another record to the datafile that the program is creating.

======

WHY BOTHER???

Think about it... although cumbersome to explain, the program will make it easy to generate the datafiles quickly. The user will set up the values for all of the fields except the "answer" field. THEN he/ she will type in the answer, and almost instantly the computer will be ready for the answer to the next question. Usually, the user will just type answer, answer, answer.... It will only be when sections or chapters change that anything else needs doing, and when something else needs doing, it will be easy and intuitive to do it.

Enough introduction... let's look a the Delphi behind the application.

===

Start a project. Do an initial save, establishing a folder for all things connected with this demo. Set the project up with a form named DD75f1 and a unit named DD75u1.

Put 6 edit boxes on the form. Normally, I'd call them eF1, eF2, eF3... eF6, but for this I'm just going to accept the default names... Edit1, Edit2...

You can put them on the form quite carelessly, but do put them on in a rough column. Get the top and bottom ones in pretty much the right places.

When you have all six on the form, select them all. (Just drag a box out which encompasses every part of every edit box.) Use the Object Inspector to set their widths to 120. If you have all six boxes selected, then just putting 120 into the Width property cell, and moving to another cell will set all six box's width's to 120.

Carefully right click inside one of the selected boxes. (This will bring up the context menu without deselecting any of the boxes.) Click on Align. Request Horizontal: Left Sides, Vertical: Space equally. Click "OK". Hey presto! The boxes should be neatly laid out.

If you want to, give each one a label.

===

So: We have our basic form. Now we have to make it work the way it is supposed to.

In this tutorial, we are going to take many shortcuts, in order to keep to the heart of the ideas special to this application. In the real world, there are many "frills" that you would want to add.

The first shortcut that we will take is to accept that the program can only create new files. It can't add to an old one. It can't even edit data it has created. All of that can easily be done in a text editor from "pieces" initially created by the program.

The data will be written to a memo at first, and then there will be a way to save the memo to a file.

So...

1) Add a memo to your form. Set its word wrap property to false. Make its "Lines" property empty.

2) Add a button to the form; call it buSave, and make it's caption &Save.

===

So far, so boring.

Click on Edit1, so that it alone is selected. Use the Object Inspector to start an Edit1Change procedure. Make it....

procedure TForm1.Edit1Change(Sender: TObject);
begin
if length(Edit1.text)=2 then Edit1.color:=$0000FF00 //no ; here
  else Edit1.color:=$00aaaaff;

.. and then run the program, putting different values into Edit1. You should find that when there are 2 characters in the edit box, the right number for the first field, the background is green, and when then number of characters is not right, the background is pink. (A red background was hard to see the edit box contents against.)

That's a start. Now we're going to fancy things up a bit.

Just below...

  private
    { Private declarations }

... add...

bE1,bE2,bE3,bE4,bE5,bE6:byte;
bSE1,bSE2,bSE3,bSE4,bSE5,bSE6:byte;

These are variables to monitor whether the text in Edit, Edit2, etc is currently of the right length.

Double click on an empty part of the form to start, and then finish by hand,,.,

procedure TForm1.FormCreate(Sender: TObject);
begin
bSE1:=2;
bSE2:=3;
bSE3:=2;
bSE4:=1;
bSE5:=3;
bSE6:=1;
end;

These variable set the "correct" length for each field.

Modify Edit1Change, making it...

procedure TForm1.Edit1Change(Sender: TObject);
begin
if length(Edit1.text)=bSE1 then begin
    Edit1.color:=$0000FF00;
    bE1:=0;
    end//no ; here
  else begin
    Edit1.color:=$00aaaaff;
    bE1:=1;
    end;
end;

What this accomplishes is that bE1 is 0 if the value in the edit box is the right length, and it is 1 if the value is the wrong length.

Later in the program we are going to ask what....

bE1+bE2+bE3+bE4+bE5 equals. If it equals 0, then all fields are filled with values which have the right number of characters.

Now create similar OnChange handlers for Edit2, Edit3, Edit4, Edit5. (Don't do one for Edit6 yet.) You can't simply copy and paste the following. You need to double click on each edit box in turn, to properly initialize the code as the OnChange handler.... but then you can copy/ paste the code for each, or create it with a past of a general form and just inserting the 2s, 3s, 4s, etc afterwards. Didn't take me more than ten minutes.

procedure TForm1.Edit2Change(Sender: TObject);
begin
if length(Edit2.text)=bSE2 then begin
    Edit2.color:=$0000FF00;
    bE2:=0;
    end//no ; here
  else begin
    Edit2.color:=$00aaaaff;
    bE2:=1;
    end;
end;

procedure TForm1.Edit3Change(Sender: TObject);
begin
if length(Edit3.text)=bSE3 then begin
    Edit3.color:=$0000FF00;
    bE3:=0;
    end//no ; here
  else begin
    Edit3.color:=$00aaaaff;
    bE3:=1;
    end;
end;

procedure TForm1.Edit4Change(Sender: TObject);
begin
if length(Edit4.text)=bSE4 then begin
    Edit4.color:=$0000FF00;
    bE4:=0;
    end//no ; here
  else begin
    Edit4.color:=$00aaaaff;
    bE4:=1;
    end;
end;

procedure TForm1.Edit5Change(Sender: TObject);
begin
if length(Edit5.text)=bSE5 then begin
    Edit5.color:=$0000FF00;
    bE5:=0;
    end//no ; here
  else begin
    Edit5.color:=$00aaaaff;
    bE5:=1;
    end;
end;

Yes... I know I haven't done an OnChange handler for Edit6.

It was special, remember?

By the way: Delphi is capable of more elegant programming. You could, if you wanted, have one event handler to take care of what we have done with 5 handlers. Doing it the pedestrian way Gets The Job Done, though. With copy/paste, it really doesn't take long to generate all the similar handlers.

Here's Edit6's OnChange handler....

procedure TDD75f1.Edit6Change(Sender: TObject);
var sTmp:string;
begin
if length(Edit6.text)=1 then bE6:=0 else bE6:=1;
if bE1+bE2+bE3+bE4+bE5+bE6=0 then begin // only add a line if all the fields are
     //of the correct length.
 sTmp:=Edit1.text+Edit2.text+Edit3.text+Edit4.text+Edit5.text+Edit6.text;
 memo1.Lines.add(sTmp);
 Edit6.text:='';
 end;//of if
end;

To avoid a problem that will arise in certain circumstances, we need to make an addition to the other OnChange handlers, e.g.....

procedure TForm1.Edit1Change(Sender: TObject);
begin
if length(Edit1.text)=bSE1 then begin
    Edit1.color:=$0000FF00;
    bE1:=0;
    Edit6.text:='';
    end//no ; here
  else begin
    Edit1.color:=$00aaaaff;
    bE1:=1;
    end;
end;

See the....

    Edit6.text:='';

.... in that, just before the first "end"? One just like it has to be added to the other OnChange handlers, too, i.e. the ones for Edit2 through Edit5.

===

A detail: Set Edit6's Tab Order property to zero. this will place the cursor in Edit6 when the application starts up, which will be where you usually want it.

===

By now, you are probably tired of re-changing the contents of Edit1.text - Edit5.text at the start of every run of this program.

Help is at hand!

In procedure TDD75f1.FormCreate, add....

Edit1.text:='tk';
Edit2.text:='f01';
Edit3.text:='01';
Edit4.text:='a';
Edit5.text:='005';
Edit6.text:='';

Edit1Change(self);
Edit2Change(self);
Edit3Change(self);
Edit4Change(self);
Edit5Change(self);

Make the values anything you like, but they should be the right lengths AND Edit6.text should be "", i.e. empty.

The calls of Edit1Change, Edit2Change, etc, set the variable bE1, bE2, etc, and set the background colors in the edit boxes. It would be easy to specify an initial value of a wrong length, and checking that this hasn't happened is easily done, as above. bE1, bE2, etc need to be given initial values, in any case.

===

Mistakes happen. Therefore we're going to add a button to remove the last line from the memo. Put a button on the form, name it buRemoveLast, and give it the caption &Remove Last.

Double-click on it to create an OnClick handler skeleton, fill it in as follows....

procedure TDD75f1.buRemoveClick(Sender: TObject);
begin
if memo1.lines.count>0 then
     memo1.lines.delete(memo1.lines.count-1);
end;

===

It's all very well creating the memo, but what we need is a file! That's what the Save button is for.

First add a SaveDialog component to your form. (Doing this is a bit like adding a button, but you have to go to the Dialogs tab of the component palette, and it does not give rise to anything that you see on your finished application. In the design view, you will see a small square on your form, which you can place anywhere convenient.)

Double-click on it, to bring up the buSaveClick skeleton. Fill it in as follows....

if SaveDialog1.Execute then
    memo1.Lines.SaveToFile(SaveDialog1.FileName);

There are lots of clever things you can do with the properties of the SaveDialog component... but we don't need them for what we are doing here. We're done creating the OnClick handler for the Save button! (Make sure it works.)

===

The last critical element of the program that we must implement is the automatic incrementing of the fifth field. That field holds the number of the question that field 6 holds the answer to. Field 5 will change with each record, and usually in a predictable way, so the chore of changing it can be given to the computer. I say "usually" because sometimes question "numbers" like "25A" and "25B" arise. We can even cope with this... but it will mean that the application's user will have to enter the question "number" by hand when it isn't being cooperative.

Start by creating a procedure to put 'xxx' in Edit5. Not very helpful, but it gives us something that alters the contents of Edit5 (Edit5.text), and we can then deal with making the application call that procedure at the right time, and then we can move on to having the procedure do the clever thing we need. Always try to develop your applications a bit at a time, as we are doing here. Note that we can see that we've got "that much" done at each stage.

So... A procedure to put 'xxx' in Edit5.text....

Type the following into your code, just above the "end." at the end of all of it. Don't try to run the application yet.

procedure TDD75f1.FillField5;
begin
Edit5.text:='xxx';
end;

Now, up at the top of the code, add just one line.... the one before "public" in the snippet of the whole program which I show below, to help you see where that line, "procedure FillField5;",goes.

  private
    { Private declarations }
    bE1,bE2,bE3,bE4,bE5,bE6:byte;
    bSE1,bSE2,bSE3,bSE4,bSE5,bSE6:byte;
    procedure FillField5;
  public
    { Public declarations }

Add a temporary button to your form. Leave the name unchanged. Double-click on it, and fill in the OnClick handler skeleton with....

procedure TDD75f1.Button1Click(Sender: TObject);
begin
FillField5;
end;

Run the program. Clicking the new button should fill Edit5 with three x's.

Remove the button. It was just there to test the new procedure. Do NOT delete all of the button's OnClick handler. Delete just the bit you added, leaving the handler like this....

procedure TDD75f1.Button1Click(Sender: TObject);
begin

end;

The nest time you run or save the application, the scraps will be removed AND the other bits relating to this handler, which Delphi put in for you, of which you were probably unaware. (The necessary declarations up in the type definition. Still there, from the "Save" button, for instance, you can see "procedure buSaveClick(Sender: TObject);".

===

So.. we've got a way to fill Edit5.text with three x's. Now we want to make that happen when it should.

One of the delights of a well designed program is that it is incredibly easy to "do things".

In this case, all we need to do is add one line to procedure TDD75f1.Edit6Change. The new line, "FillField5;", as I hope you guessed, goes just after the "Edit6.text:="";", making the last lines of TDD75f1.Edit6Change....

 memo1.Lines.add(sTmp);
 Edit6.text:='';
 FillField5;
 end;//of if
end;

Make sure that works. Don't, by the way, expect "xxx" in the record produced when "xxx" is put into field 5. But, unless you change the "xxx" by hand before entering another answer in field 6, you should see xxx's in subsequent records produced by the application. (This behavior is what we want.)

===

Now we move on to making FillField5 more clever.

procedure TDD75f1.FillField5;
var iValue,iCode:integer;
    sTmp:string;
begin
val(edit5.text, iValue, iCode);
if iCode=0 then begin
   iValue:=iValue+1;
   sTmp:=inttostr(iValue);
   while length(sTmp)<3 do sTmp:='0'+sTmp;
   if length(sTmp)>3 then iCode:=1;//flag error arising from incrementing 999
   Edit5.text:=sTmp;
   end;
if iCode<>0 then //Note that iCode may have started as
   //0 after the call to val(... , but been changed to
   //1 in the block under "if iCode=0 then...
   Edit5.text:='';//Clear it if what was in it before
   //wasn't a number, or if it was 999, in which case
   //the next number is too big to hold in 3 characters,
   //the limit imposed by the program's requirements.
//Edit5.text:='yyy';
end;

That's the essentials done. Now three frills....

First Frill....

In order to make sure that only a,b,c,d or e gets entered in Edit6.text...

Put a label on the form near Edit6. Name it laWarn. Make its caption nothing.

Use the Object Inspector to create a skeleton OnKeyPress event handler and fill it in as follows...

procedure TDD75f1.Edit6KeyPress(Sender: TObject; var Key: Char);
begin
if pos(Key,'abcde')=0 then begin
    Key:=chr(0);
    laWarn.caption:='Only a,b,c,d or e allowed';
    end//no ; here
  else
    laWarn.caption:='';
end;

Second Frill....

If you need to change the value in one of the edit boxes, you will probably want to delete what is there first. The following will cause the contents of the edit box to be selected when you enter the box. if you simply type what you want, the selected text will disappear as soon as you press the first key. Not only are we going to arrange this, but we are going to get it done by a procedure shared by all of the edit boxes using it, i.e. Edit1 through Edit5. (Don't apply the procedure to Edit6... it has it's own arrangements due to it's special takes-only-one-character, and triggers-write-record-to-memo status.

First I selected Edit5, and, using the Object Inspector, started an OnClick event handler skeleton, which Delphi called EditClick... no surprise there.

I put showmessage('test') between the "begin" and the "end".. this to keep Delphi from removing it on the next save or run, and to test that it was working.

I then, by hand, copied the whole thing, changing the name to EditSeveralClick. I changed the message displayed to 'test2'.

IMPORTANT::: I ALSO copied...

procedure Edit5Click(Sender: TObject);

... up in the type definition at the top of the code, and inserted, with copy and a little edit, just beneath it....

procedure EditSeveralClick(Sender: TObject);

I then selected the first five edit boxes, Edit1 - Edit5, and went to the Object inspector. The pull-down for "OnClick" listed my "EditSeveralClick", and this I selected.

I then ran the application. Clicking on any of the first five edit boxes had the expected result... the message "test2" came up in a message box.

I deleted the "Showmessage('test'); line (only) from Edit5Click, and saved my code. The empty skeleton of Edit5Click, and the declaration up at the top, were removed by Delphi.

(Yes, I could have used "Edit5Click" for the five edit boxes, but illogical names like that can lead to troubles.)

Now to make the OnClick handler do something useful! Change it to...

procedure TDD75f1.EditSeveralClick(Sender: TObject);
begin
if sender=Edit1 then Edit1.selectall;
if sender=Edit2 then Edit2.selectall;
if sender=Edit3 then Edit3.selectall;
if sender=Edit4 then Edit4.selectall;
if sender=Edit5 then Edit5.selectall;
end;

The "secret" is in the Delphi supplied entity "sender". From the above you can see what it does.

I can't believe that what I wanted to do has to be done so crudely, but you can't, for instance, use "sender.selectall;" I tried changing the type of the sender variable to TEdit, both where the procedure is coded, and above, where it is declared, but that seemed to make it ineligible as an event handler, at least one that the Object Inspector could help with. Again... any help with this welcome.

Third Frill....

(By the way.... How is a violist like a seamstress? They both tuck up frills. Don't tell the school web-police that's in here. I promise, such a riske joke VERY rare! If you understand it, you don't need "protecting" from it anyway, do you?)

While such things can be overdone, I thought I'd "fix" things so that, for Edit6, entering "1" would give "a", "2" "b", "3" "c", etc...

If you're going to do this sort of thing, do put a message on the screen next to the deranged control?

So... how to do this for this program....

1) In Edit6KeyPress, change....

if pos(Key,'abcde')

... to....

if pos(Key,'abcde12345')

2) Also in Edit6KeyPress, just after the "else", add....

if key='1'then key:='a';
if key='2'then key:='b';
if key='3'then key:='c';
if key='4'then key:='d';
if key='5'then key:='e';

I could do this "cleverly", if I wished, but I think the clarity worthwhile.

====

One thing this program really should have is a mechanism to save you from yourself. If you click "Quit" before clicking "Save", your work will be lost. You should get the usual question about "Don't you want to save your work?"

I have implemented this in the machine readable form of the finished program which should be online with this tutorial. It is in the form's OnClose and OnCloseQuery routines. the state, enabled or not, of the Save button is also involved. An explanation what is going in is in the tutorial about DD74. It is really very simple, and worth doing in any program that creates things to be saved.


To search THIS site....
Click this to search this site without using forms, or just use......
powered by FreeFind
Site search Web search
Be sure to spell the words you are searching for correctly!
The search engine doesn't understand English. Searching for "How do I use repeat" will just return pages with "how", "do", "I", "use", or "repeat".
(Go to my other sites (see bottom of page) and use their search buttons if you want to search them.)...


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!!! Sheepdog Software (tm) is supposed to help do that, so if you found 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
How to email or write this page's editor, Tom Boyd




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