Dec 102017
 
Text file describing ways of optimizing Clipper's DBEDIT function with examples.
File DBEDIT.ZIP from The Programmer’s Corner in
Category Dbase Source Code
Text file describing ways of optimizing Clipper’s DBEDIT function with examples.
File Name File Size Zip Size Zip Type
DBEDIT.TXT 14098 5120 deflated

Download File DBEDIT.ZIP Here

Contents of the DBEDIT.TXT file


Brice de Ganahl
Software Management & Development
Deerfield Beach, Florida
ID: 76244,1152

The following article was written for The Sunshine Clipper. This
newsletter is published collectively by various user groups in
Florida, edited with great care and dedication by Terry O'Neill, is
published on a monthly basis and is available for $10 annually.



HARNESSING THE WILD HORSE

Dbedit() can be described much the same as a wild horse: Powerful,
beautiful and fast, but also unruly and wild...

You can be tossed aside (sometimes fatally) when it gets "angry", or
you can go for the ride of you life. For those who have never tried
dbedit(), do not be afraid, it is only wild when you dig your code
into its sides and thrash it for better performance.



The Basics:

Dbedit() is, essentially, Clippers answer to the dBase BROWSE command.
Issued by itself (i.e. with no parameters) dbedit() will display, in a
table format, the fields from the currently SELECTed file. Above each
column it will place the field name as defined in the file. In each
column it will place the data from the file (in its indexed order).
Using the standard cursor movement keys, dbedit will move about in the
file much as you would expect. If the file has more fields or records
than will fit within the screen boundaries, dbedit() will scroll new
data onto the screen at the users request. All of this with one short
line of code.....DBEDIT().

In order to offer increased power and flexibility, Nantucket chose to
leave all functionality, with the exception of the viewing mentioned
above, in the programmers hands. In order to access this flexibility,
the programmer must pass various parameters to dbedit.


The Parameters:

- Boundaries
Dbedit()'s first four parameters define the screen boundaries of the
table. The horse gets a little jumpy when its master defines these
boundaries. If there are two fields in the file, each with a width of
twenty, and you simply add the two together and decide that a width of
forty is adequate, your horse will simply jump the fence. Dbedit()
will automatically insert a column divider with a space on each side
(3 columns) between your two fields thus requiring forty three columns
total. Each additional field requires an additional three columns for
its divider. The horse will not dump you on the ground (DOS), it will
simply allow you to view one field or the other but not both
simultaneously. The only trick then, is to remember to include in the
column spacing when you calculate the total width of the dbedit()
window.

- Data definitions
The fifth parameter that you almost invariably will want to pass
dbedit() is an array describing what you want dbedit to list in each
column (the default is all fields in the currently SELECTed file).
Each element of the array defines the content of one column of data.
If you have a file with ten fields but only want your user to view two
of them, then DECLARE an array of two elements and assign a value to
each of the two elements (see examples below).

At first, the deceitful filly that she is, dbedit() had me convinced
that only DBF field names would be accepted. Most often I will use
this array with just field names, but it will eat about anything I
choose to feed it. (Careful though, a few things get the beast
excited; I've been tossed quite abruptly to DOS on more than one
occasion.)

If you want to view the two fields that were mentioned above, and
their names in two RELATED files are alias1->corn and alias2->wheat,
DECLARE an array with two elements and assign the two elements as
follows:

DECLARE flds[2]
flds[1] = "alias1->corn"
flds[2] = "alias2->wheat"

If you NEED to fit the viewing of these two fields into a space of
forty (instead of forty three), try this:

flds[1] = "alias1->corn"
flds[2] = "SUBSTR(alias2->wheat, 1, 17)"

Another trick to provide the end user with a comfortable saddle when
riding the tamed beast is to put a constant message in any field that
has not been assigned a value. To do this try:

flds[1] = "alias1->corn"
IF EMPTY(alias->wheat)
flds[2] = ["crop undefined "]
ELSE
flds[2] = "alias2->wheat"
ENDIF

Note that the message 'crop undefined ' is padded with three spaces
making it a length of twenty. This is a must. Dbedit will provide some
interesting (if not unruly) results if the evaluated length of this
expression EVER changes. Note also the double delimiters [""] around
CROP UNDEFINED. Dbedit() evaluates what is inside the first set of
delimiters at run time. Therefore, without the double set, dbedit()
would look for a field or variable named CROP UNDEFINED and not
finding it would buck like a bronco and land you squarely in Errorsys.


- The UDF
The most significant parameter to the immense functionality of
dbedit() is the sixth parameter, a user defined function. This is the
beauty and the beast (and the last parameter to be discussed as the
remainder are purely cosmetic). I have had this UDF as short as ten
lines, and, including functions called from it, as long as several
thousands.

It helps to view dbedit() and its UDF as a wait state similar to READ
with a VALID UDF. Both sit and do nothing until a key is pressed. When
a key is pressed, each has a defined action to take. If the ENTER key
is pressed in the READ, control is passed to the VALID function. In
dbedit(), the processing of this "valid" function occurs EVERY time a
key is pressed (or forced with the KEYBOARD command). When dbedit()
executes your function it passes to it two parameters: a status
parameter and the current field pointer. The status is a code that
dbedit() generates based on it's condition: idle, top of file, end of
file, file empty, or key stroke exception. The IDLE is the most
confusing and important and will be discussed in detail here. The other
four are well documented and obvious as to their functionality.

At first, IDLE was difficult to understand but I think only because it
was not documented very well. Basically, IDLE occurs whenever
dbedit() has moved the cursor/file/field pointers and has no more
"cursor movement keys" pending in the keyboard buffer. If, for
example, the user presses a down arrow, dbedit() will move the file
pointer down one row first, and then execute your UDF with a status of
zero. (Only after you return from your UDF will dbedit() repaint the
cursor at the new file pointer location, thus the annoying flicker of
the cursor).

There are 14 keys which dbedit() considers as "cursor movement keys".
(These 14 keys are the first 14 keys listed on page 6-53 of your
manual.) That is, dbedit() moves automatically when any of these 14
keys are pressed without asking your UDF about it first. After dbedit
has made the move then the UDF is called after the fact with the
status of zero. This is a real problem. What if you want to keep your
user within a certain range of a large file. If the user presses a
down arrow and you are already on the last item of the range, dbedit()
will move first, and then let you validate whether you want to be
there or not (by jumping to your UDF). Once a record is on the screen
it is quit difficult to get dbedit to back it off. So your not lying
in the middle of a field of DOS, but your not in the hay either.

The first solution would seem to be SET FILTER TO , but
those who have tried this have found that dbedit() looks through the
rest of a file after the last condition has been met causing
unacceptable delays. For example, if you have a file of all your farm
animals' names and you want to show the user only the names of your
cows you might SET FILTER TO alias->genus == "cows". However, if the
cursor is sitting on the last cow "Zelda", and you have three thousand
horses, when the user hits the down arrow, dbedit() will unwittingly
search all three thousand horses looking for another cow (even if the
file is index on genus+name).

One trick to managing the cursor movement keys around this problem is
to capture them all in a SET KEY TO procedure. This
traps the cursor key before dbedit() can react. Most of the time you
actually want the down arrow to move down, however, so you'll have to
KEYBOARD the key every time the remains valid for downward
movement. Before you KEYBOARD the key, however, you have to turn the
hot key procedure off in order to avoid an endless loop between
dbedit() and your hot key procedure. The following code exemplifies
this with a excellent feature that Gary Herbstman suggested: "why not
let the hot key procedure be the same as the main dbedit() UDF?".

This code uses the simple example of the down arrow trying to cursor
past a predetermined "out of bounds" record in order to clearly show
the technique being used. The situation gets more difficult, however,
when you have to also consider the pgup, pgdn, ^pgup and ^pgdn.

The decision you have to make here will depend on your application. If
you suspect the user will be in the dbedit paging about for a long
time, you will want to create two arrays of record numbers each
containing an equal number of elements. The number of elements should
equal Dbedit()s window height (i.e. the magnitude in records of a page
up or page down). Before executing dbedit() fill the arrays, one with
the first page's record numbers and the other with the last page's
record numbers. Then, when you detect a pgup/pgdn condition you can
simply ASCAN() to see if the current RECNO() is in the top/bottom
array. If it is, then either ignore the key altogeter, or KEYBOARD the
equivalent of your GO-TOP/GO-BOTTOM key.

If you suspect that the user will not be paging about much, do not
invest the initial time and space in the arrays. Instead, whenever you
detect the pgup/pgdn condition simply skip the number of records in
the file and test for the condition. If the condition that you test is
found to be unacceptable, you do not need to reposition the file
pointer. Dbedit keeps and internal file pointer which can only be
repositioned when dbedit() is aware that the Clipper file pointer has
changed. This is not the case in a HOT KEY situation.

*************************************
* This example will show
* handling the down arrow only.
* It assumes that last_cow() returns
* the recno() of the last cow in
* the indexed list.
*************************************
SELECT farm_anmls

* Last cows address
last_cow = last_cow()
DECLARE array_flds[2]

* Hide anything other than "cows" from
* the user with the following conditions.
*************************************
array_flds[1] = "IF( (genus # 'cows'), SPACE(LEN(genus)), genus )"
array_flds[2] = "IF( (genus # 'cows'), SPACE(LEN(name)), name )"

DBEDIT( t, l, b, r, array_flds, "myfunc" )



RETURN
*************************************
* A UDF for dbedit.
*
*
*
*************************************
FUNCTION myfunc
PARAMETER status, fld_ptr

PRIVATE ret_val

* If acceptable condition for down arrow,
* turn off the hot key procedure
* and stuff the keyboard. If the TYPE()
* of status is not equal to zero, we know
* that we arrived here via the hot key.
*************************************
IF LASTKEY() == 24 .AND. RECNO() != last_cow .AND. TYPE( "status" ) # "N"

SET KEY LASTKEY() TO
KEYBOARD CHR(LASTKEY())
ret_val = 1
* The hot key is now turned off.
* Dbedit will proceed by handling
* the KEYBOARD key as though the
* user had pressed it, but we
* have filtered an out of bounds
* condition
**********************************

ELSEIF TYPE( "status" ) # "N"

* Out of bounds was detected if
* we are here. Return to dbedit() but
* request nothing of dbedit. You
* will note that dbedit() does not
* even flicker with this action.
**********************************
ret_val = 1

ELSE

* After dbedit() handles the
* stuffed down arrow it will return
* here with a status of zero. So,
* turn the hot key(s) back on.
**********************************
IF status = 0
SET KEY 24 TO myfunc
SET KEY crsr_key2 TO myfunc
SET KEY crsr_key3 TO myfunc
* ...etc.
ret_val = 1
ELSEIF status =
.
. other conditions to test for
.
ENDIF
ENDIF

RETURN ret_val


This code uses the simple example of the down arrow trying to cursor
past a predetermined "out of bounds" record in order to clearly show
the technique being used. The situation gets more difficult, however,
when you have to also consider the pgup, pgdn, ^pgup and ^pgdn.

The decision you have to make here will depend on your application. If
you suspect the user will be in the dbedit paging about for a long
time, you will want to create two arrays of record numbers each
containing an equal number of elements. The number of elements should
equal Dbedit()s window height (i.e. the magnitude in records of a page
up or page down). Before executing dbedit() fill the arrays, one with
the first page's record numbers and the other with the last page's
record numbers. Then, when you detect a pgup/pgdn condition you can
simply ASCAN() to see if the current RECNO() is in the top/bottom
array. If it is, then either ignore the key altogeter, or KEYBOARD the
equivalent of your GO-TOP/GO-BOTTOM key.

If you suspect that the user will not be paging about much, do not
invest the initial time and space in the arrays. Instead, whenever you
detect the pgup/pgdn condition simply skip the number of records in
the file and test for the condition. If the condition that you test is
found to be unacceptable, you do not need to reposition the file
pointer. Dbedit keeps and internal file pointer which can only be
repositioned when dbedit() is aware that the Clipper file pointer has
changed. This is not the case in a HOT KEY situation.


** Happy trails, Partner!!!


 December 10, 2017  Add comments

Leave a Reply