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

Delphi: A small illustration: Reading Skill Exerciser

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

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

Click here if you want to know more about the source and format of these pages.
This tutorial is accompanied by the full sourcecode for a program that allows a user to exercise an important reading skill. The program displays a number of words on the screen... but only briefly. It then puts another word on the screen, and asks the user whether that word was in the list of words displayed previously. The number of words shown can be altered, as can how long they are displayed.

Zipped .exe and sourcecode for the program discussed here.

Within the sourcecode there are illustrations of how to achieve things with Delphi. You already know what the user needs to know about the program's operation. Now I'll turn to aspects of the program which would only interest a programmer.

In a fit of laziness, I decided to hold the program's vocabulary in a memo. There's just one word on each line. The memo is hidden, so the user will not be aware of it. This was perhaps not the most elegant way to hold the words, but it does give you a chance to see memos at work. By the way: This program is not intended as a commercial product in its current form. A commercial product, for example, would need a way to change the list from which displayed words were drawn. For the sake of this "how to program" demonstration, the words were put into the memo "at design time". In other words, I used the Object Inspector to access the Lines property of the memo. Clicking on the [...] at the right hand end of the Lines item brings up Delphi's String List editor. I typed the words I wanted, one to a line, and clicked 'OK'. I set the memo's WordWrap property to false. During the programs's FormCreate I say memo1.hide. Job done; words available!

The program starts up poised to go, and there's a "Go" button to start it running as soon as the user is ready to proceed with the exercise. There's a menu with a way to change how long the words appear, and to change how many words appear in each set..

Once "Go" is clicked, the following happen:
Words are selected to be displayed.
A word is selected to be presented in the "Was <word> shown?" question.
The words are displayed for a period of time controlled by a timer.
Once the words disappear, the question is displayed.
As soon as the user responds, feedback and the next problem appear.

This continues until the user clicks on the "Quit" button... which terminates the program. (Another bit of crudeness you would improve in a commercial version.)

Let's look at details of some of those stages. You might want to look at the sourcecode as we go through these points.

Note how when "Go" is clicked, the "Go" button is hidden. Just a minor bit of tidiness, but it is worth managing things thus. It makes no sense to have a "Go" button available when the program is already running. One school of thought would say that rather than use buGo.hide;, I should have used buGo.enabled:=false; That leaves the button visible, but grayed out. Both approaches have their place. I would lean more towards the enabled:=false approach when there are circumstances in which the button might again become available. Having it visible but grayed out tells your user that sometimes "Go"ing is an option, but not at the moment. In the program we are considering, you never "Go" again after the first click on that button.

The event handler for buGoClick is only about 8 lines long. This means you can study it easily, there isn't too much to assimilate, it is too exposed to provide many hiding places for bugs. So how does it achieve so much? The "secret" is in the little word "ChooseAndDisplayWords". That's quite an involved business, and one that we'll need again later, so it is a good idea to parcel it up in a discrete procedure. Learning what should and what should not be put in procedures is an important part of mastering the art of programming in Delphi, and other languages. Good use of procedures and functions is also a valuable mental discipline, forcing you towards having a clear idea of what is required from each part of your program.

In the case of this program, the job of ChooseAndDisplayWords is to pick the words that are to be presented for reading, pick the word that the user is going to be asked "Was it in the list?", make a note of whether the word is or is not in the list, and put the words on the screen. Depending on your background, you may need to become "reckless" with variables. If in doubt, use another variable!

By the way: I'm not sure I could have written ChooseAndDisplayWords less elegantly. It does the job there's certainly nothing pretty about the algorithm!

Going back to the variables filled during ChooseAndDisplayWords: The words in the list are put into saWordsInQues[1..n]. ("sa" for "string array". If you don't understand arrays, you need to! This program makes some use of them, for instance in the line that says...
for c1:=1 to bPossWordMax do saWordsInQues[c1]:='';
This empties saWordsInQues[1], saWordsInQues[2], saWordsInQues[3]... prior to their use. (Probably unnecessary, as the program turned out, but in general a good idea.)) The program then puts words in an appropriate number of the elements of saWordsInQues, checking along the way that no word is in the list more than once.

ChooseAndDisplayWords puts the word for "Was it in the list" into sAskIfInList.

ChooseAndDisplayWords puts "true" in boWordInList if it was, and "false" there if it isn't.

And it puts the words on the screen. As long as ChooseAndDisplayWords accomplishes the above, we don't have to worry about its innards. This is beautiful because
a) When we are looking at the main code of buGoClick, and at the other place where ChooseAndDisplayWords is used, we are saved any distraction from the details,
b) If our sense of beauty is sufficiently offended, we can go back later and improve the code within the procedure, without any concern about anything else in the program. The code could be re-worked by someone who never had, never would see the rest of the program
c) If we choose to make the "list of words" something more sophisticated, that is certainly possible. In fact, I intend changing the program so that instead of a random jumble of words, the user sees a sensible sentence. ChooseAndDisplay words will become somewhat more complex to achieve this, but the outside world will still only see the same simple outputs: What to display, what to ask about, and whether that's in the stuff displayed.

So... moving on. Another part of "Go" was to start the timer. When the timer times out, the words to be read disappear and the "Was <word> shown?" question appears. The timer is disabled, to prevent further timeouts for the moment. eAns.setfocus is equivalent to clicking on the eAns edit box, which prepares things for the user to enter "Y" or "N" to answer the question.

Then the program just sits there until the user presses some key. As soon as that happens, the eAns OnChange event occurs, taking us to the code for that.

Again: The event handling code doesn't look like much... because a bunch of related matters have been parceled up in CheckAns. Not just for tidiness, but again because all that stuff is needed twice.... once in the handling of if the user typed "Y", and once in the handling of if the user typed "N". Notice that the program is the perfect parent: The user can type (almost) anything, and the program will just ignore it... unless it is "Y" or "N", the two sensible answers.

Details: The sTmp:=uppercase(eAns.text) at the start of the eAnsChange handler means that whether the user types "y" or "Y", the program will behave as if the user typed "Y".
The eAns.selectall at the end of the eAnsChange handler is there so that when the user answers the next question, that answer will overwrite any previous answer. The remmed out "eAns.text:='';" made sense, and might have worked, but i was getting some inputs double counted. That may have arisen because changing eAns within the eAns OnChange handler was asking for trouble, but I think there was some unintended double use of sTmp in the code.

So... still looking at the eAns Onchange handler: Whether the user presses "Y" or "N", the program executes CheckAns (more on this in a moment). It clears away the question about "Was <word> shown?". It restarts the timer. And it sets up the next question. All very tidy!

One little diversion, before we look at CheckAns. I've not found explicit confirmation of something that I hope is true.... I hope that if a timer is not enabled, and you then enable it, the timer starts counting down from the "top" of its full interval. That is to say: If the interval is set at, say, 4 seconds, and you enable the timer, I hope (and believe!) that you will get the full 4 seconds before the first timeout. This is the sort of optimistic assumption that can get you in trouble in programming! I'm 99% certain that if you leave the timer enabled, you will get an OnTimer event every 4 seconds... I'm just not certain how to ensure that you get a full 4 seconds before the first OnTimer event.

Back to work: The details of CheckAns. First of all, I trust you understand that if you cause the CheckAns code to execute with "CheckAns(sTmp)", then whatever is in sTmp will be transferred to sTmpLoc within the code for CheckAns. This is called passing parameters, is very important generally, and is used in a simple way here.

CheckAns is quite a nice little procedure, with two procedures of its own. Jump down past their definitions to

As the rem says, this Boolean variable seems an unnecessary encumbrance. It is used to make a note of the need (or lack thereof) to add 1 to bNumRight shortly. (Again, part of my messy fix of the double counting problem.)

The four "if" statements are neat and tidy, their logic being (I hope) clear. The messy details of what you do when the user was right or wrong are neatly tucked away in the small procedures for those circumstances. Look at them now.

"if (sTmpLoc='Y') or (sTmpLoc='N')..." is probably unnecessary... I don't think CheckAns is called in any other circumstance... but it doesn't hurt to be careful! When "Y" or "N" has been entered, various things need to be dealt with, and you can see them in the sourcecode. the "if iNumberDone>3.." test is there to suppress the "percent right" reporting until a reasonable number of problems has been done.

That covers the meat of the program. One side-dish deserves comment:

The menu offers a way to change both the number of words in each list, and the time for viewing them. In both cases, the only answer that makes sense is an integer, but the InputBox function is quite capable of accepting any characters. We could devise complicated routines to look at the user's response, something along the following lines.....
 sTmp:=InputBox('How Many','How many?",5);
until sTmp <is made up only of digits>
... and then do

It wouldn't be too hard, but why go to the hassle? And why not start getting comfortable with the try... except... construct, which will solve much more complex problems, as well as taking care of this little one.

The basic idea with "try" is that the program will do the things from where "try" appears quite normally, and will skip over the stuff between "except" and its "end" EXCEPT when something goes wrong. If, for instance, you try to convert something from a string to an integer which cannot sensibly be converted, strtoint quite helpfully "raises an EConvertError exception". (The strtoint help file entry tells you this.) When an exception is raised, if you are in a try.. except... block, then everything else, down to the "except" is SKIPPED, the program does anything it ought to according to the "except" part, and then continues after the Except's "end".

If you look at the procedures for WordsPerSet1Clcik, and TimeToView1Click you will see that with a little judicious use of boTmp, it is possible to force a user to give sensible answers to the questions before the user can proceed. Possible... and easy!

So! I hope that's shown you some useful ideas. Please remember that I myself plan to sell something not a great deal fancier than what I've shown you here, so please resist the temptation to use it as a starting point for anything too directly derivative. You an always get it from my freeware/ shareware site! :-) Sheepdog Software Store

Zipped .exe and sourcecode for the program discussed above.

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

Click here if you're feeling kind! (Promotes my site via "Top100Borland")

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. 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 Tutorials main page
How to email or write this page's editor, Tom Boyd

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

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

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