The numerous answers to that question have been discussed in such depth in the Newsgroups, that I will not spend much time on why.
The basic problem in the @ Say ... Get structure is the Read command. In procedural programming, it is common to build in stages where the program flow is waiting for the user to enter data so it may proceed to the next step where it usually processes the data just entered. With a Read statement, there is usually one intended exit point, that is the step right after the Read command. In the object oriented way of a dBASE form, there can, and often are, many exit paths. The brilliance of GUI interface combined with OOP is that many, many program flow directions are available. Whether it is a mouse click, a change in focus, a mouse movement, a timer event, a keyboard event or whatever, the impetus for the code to run next can come from many sources. That is what “event-driven” is all about.
The Read statement is not just a “have nothing to do” state, which is the normal resting state of an OOP application. It is more like the msgBox object in dBASE 7.x. The message box says, “hold your horses! There is something we need to tell the user, and we need to stop everything until they say its OK to go on.” After the user responds to the msgBox, the program flow picks up on the line immediately following the call to the msgBox. This behavior is very similar to what the Read statement does. Since the msgBox is a type of modal form, I use a model form as the platform for the @Say...Get...Read process.
Consider the following:
MuserName = space(30) Mage = 0 @ 10, 10 say "Enter Your Name: " get MuserName @ 11, 10 say "Enter Your Age: " get Mage read msgbox(rtrim(MuserName)+" you are about: "+; ltrim(str(Mage*52))+" weeks old.","Wow. You are old!") |
|
The simplicity is appealing. Many of us can write this kind of code with our eyes closed. To do it in a form is not exactly hard, but we have to keep our eyes open and we have to worry about all the events that can happen. The nice thing about a Read is that we know exactly what is going to happen next. You can “get on with your life” so to speak after you have written the code above because there is just not much that can go wrong with it. The worse that can happen is that the user can fail to enter anything, but that is predictable and easily handled.
The only problem with the code cited above, is that it won’t work in dBASE 7.x. You need a little help from a program I wrote called Terpret. It is short for interpreter, which is what it is. To actually run the code above, you need to save it to a text file. By convention, I use .scp as my extension. This is short for script. You could just as easily write it to .prg file.
Suppose you wrote the code above to test.scp.
You would run it by:
Set procedure to Terpret.prg additive Terpret("test.scp") |
|
Terpret creates a new form, lays out the text and entryfields according to @ Say and Get commands, and then opens it readmodal. Opening a form readmodal suspends all other processing until the form closes. In this way, you have not violated the OOP concept.
Remember that the variables used
in a Terpret script are the same as that for any program. That is,
when the Terpret.prg file concludes, only the variables that have references
outside of Terpret exist.
Terpret("test1.scp") |
|
Where test1.scp contains:
_app.MuserName = space(30) _app.Mage = 0 @ 10, 10 say "Enter Your Name: " get _app.MuserName @ 11, 10 say "Enter Your Age: " get _app.Mage read msgbox(rtrim(_app.MuserName)+" you are about: "+ltrim(str(_app.Mage*52))+" weeks old.","Wow. You are old!") |
|
If you were to run this script,
then both MuserName and Mage would still exist attached to the _app
frame. This is a
brute force way of saving variables and should generally be avoided.
The smarter way to do it is to pass a reference to the object which called
Terpret. This can be done like:
Terpret("test.scp",form) |
|
or
Terpret("test.scp",this) |
|
For technical reasons, form and this will not work inside of a Terpret script. Instead you have to use fform and tthis.
Suppose you issue the following
command linked to a pushbutton:
Terpret("test2.scp",form.entryfield1) |
|
And test2.scp contains:
@ 10, 10 say "You realize, of course, that you have" @ 11, 10 say "entered the following information: " @ 12, 10 say "Please fix it and try not to be so stupid!" @ 14, 30 get tthis.value read fform.text ="Don't be soooo stupid!" |
|
This will result in a form popping up and demanding that the information be fixed. When the user fixes the value, it will also fix the value of the form’s entryfield1 . Also it will change the text of the form’s title bar to encourage the user to do better in the future.
You can build in more logic too.
Mcompname = space(20) Maddress = space(30) Mcity = space(30) Mstate = space(2) Mzip = space(10) @ 10, 10 say "Enter Name: " get McompName @ 11, 10 say "Enter Address: " get Maddress @ 12, 10 say "Enter City: " get Mcity @ 13, 10 say "Enter Zip: " get Mzip read if .not. file("rolo.dbf")
if msgbox("Are you sure you want to save this?","Sure",36)
= 6
msgbox("Let's try it again.") @ 10, 10 say "Enter Name:
" get McompName
replace compname with McompName
fform.McompName = McompName
|
|
Terpret supports most dBASE flow structures.
It supports if/else/endif, do while/enddo, do case/otherwise/endcase, loop, exit, return and read. Form is supported, but you must use fform . this is supported, but you must use tthis.
Macro substitution is also supported,
but you have to code it differently. Instead of:
Mvalue = &Mtemp. |
|
Terpret does it like this:
Mvalue = :::Mtemp;;; |
|
The following are among the flow commands not supported: FOR/NEXT, ELSEIF, TRY/CATCH, DO/UNTIL, THROW, WITH/ENDWITH.
Most of these can easily be replaced using flow commands that are supported by Terpret.
Perhaps the biggest limitation
is that Terpret cannot create procedures. For instance, this won’t
work:
Mtemp = getTemp() Procedure getTemp
|
|
In other words, you cannot put multiple procedures in a terpret script.
You can call subprograms using
the Terpret command.
** TEST3.SCP Mtemp = Terpret("aGetTempFile.scp") ** AGETTEMPFILE.SCP
|
|
Passing an array to Terpret
If you are uncomfortable with a
text file sitting out there that could be edited by the user, you can pass
the script to Terpret via an array.
Mj = new array() Mj.add("Mid_no='ACME '") Mj.add("Mid_name = 'Acme Produce Company'") Mj.add("@10,10 say 'Enter I.D. #:' get Mid_no") Mj.add("@10,10 say 'Enter I.D. #:' get Mid_no") Mj.add("@11,10 say 'Enter Name :' get Mid_name") Mj.add("read") Mj.add("form.Mid_no = Mid_no") Mj.add("form.Mid_name = Mid_name") Terpret(Mj) |
|
Debugging a Terpret script
Debugging a Terpret script is a
challenge because the error message does not refer to a line that dBASE
has compiled and found to be OK. Everything in a Terpret script happens
via dBASE’s powerful macro substitution. This makes it difficult
for a developer to know exactly where in the script the error happened.
Terpret implements a debug function that helps to pinpoint the error.
The forth parameter that is passed to Terpret tells it to open a debug
window and then tell you which script line is causing the problem.
*** Test5.scp Mcompname = space(20) Maddress = space(30) Mcity = space(30) Mstate = space(2) Mzip = space(10) |
|
When you run test5.scp, it has
an error. This is because there is a missing parenthesis on the third
line. If you issue the command:
Terpret("test5.scp","","",.t.) |
|
A debug window will appear showing the commands involved. The error box will reference the line with the problem.
Conclusion
Terpret and its support of @ say ... get is a splendid example of the awesome power of dBASE’s “late binding” technology. Unlike most languages, dBASE doesn’t presume that all the logic in an application has to be known in advance. In the case of Terpret, dBASE allows entire programs to be created after the compiler has long finished its job. The author has created terpret scripts that are several thousand lines long and which add significant functionality to non-customized .exe files. Since these customer’s customized code reside in a script, they can receive many revisions of the .exe file without it affecting their customized application. The new .exe can add standard functionality without destroying customized features.
To
download the Terpret utility, click here
(it’s
a 7Kb zipped file)