Hacker’s Guide to Visual FoxPro
An irreverent look at how Visual FoxPro really works. Tells you the inside scoop on every command, function, property, event and method of Visual FoxPro.
Our fellow developers amaze us. Never have a group of such clever people created so many amazing applications. And how they do it is equally stupendous, using commands or features of this remarkable language that we knew of only peripherally, or with capabilities we only suspected. However, there are a few commands whose use should be relegated to legacy code. These commands have been replaced by better, newer or safer commands; never really had a reason to exist in the first place; or they just plain break the machine.
We divide our list into two parts: commands that should never, ever be used, and commands that should only be used from the Command Window. Some commands are just useless, and we have a few favorites that we recommend you avoid altogether. But then, there are some commands that do useful things but have too many side effects to let us feel comfortable bringing them into a client’s application.
ACCEPT
, INPUT
These commands are two of the original Xbase commands. They
were a pain to work with then, and they’re a pain now. ACCEPT
and INPUT
have no
place in our visually designed, event-driven applications. You can’t assign
picture clauses, valid routines or events to them. They’re not objects, nor can
they be subclassed. Leave ‘em alone.
DEFINE BOX
An odd duck of a command to begin with, DEFINE BOX
doesn’t
work in Visual FoxPro at all.
INSERT
Not to be confused with the very useful SQL INSERT INTO
command, the INSERT
command forces a record to be physically placed between two
others, forcing FoxPro to rewrite the remainder of the table. Now, before you
get out your poison pens and inform us that in order to run the XYZ Personal
Information Manager, you must pass it a DBF sorted in physical order, think
about some alternatives and whether you really need to rewrite all those
records, which is asking for trouble from I/O errors or power interruptions.
Consider using a SQL SELECT
statement with an ORDER BY
clause to create an
output file in a programmable order when needed.
JOIN
JOIN
creates a new table by merging two tables, potentially
adding all of the records from the second table for each record in the first—a
condition referred to as a “Cartesian join.” Why would you want to do
this? Beats us. Although you can control exactly which records of the second
table are matched with which records of the first, there is a far easier way to
do this—use a SQL SELECT
. In addition to the advantage of working with more
commonly understood syntax, SELECT
offers many more capabilities in terms of
the order of output records, the join conditions, and the form of output. This
command is one for the bit bucket. Avoid it.
SET COMPATIBLE ON
This dangerous command is made much more so because it’s accessible through the General tab of the Tools, Options dialog, represented by a check box labeled “dBASE Compatibility”. Sounds innocuous enough. After all, we all just want to get along, right? Don’t we want to be compatible?
But SET COMPATIBLE ON
is an oxymoron, as we discuss in the
Reference section. Not only does it make the language less compatible with
anything else out there, it also breaks code left and right in ways you can’t
even imagine. Originally, in the FoxBASE days, this was a way to have your cake
and eat it, too: fast Fox code wrapped within an IF FOX....ENDIF
routine, and
compatibility if you needed to compile with other Xbase languages as well. But
there are so many places where dBASE and FoxPro have parted company that this
command will give you much more grief than it’s worth.
SET DOHISTORY ON
This precursor to the Trace Window allowed you to record commands as they occurred for later dissection. Like core dumps, they were useful for dissection once the patient was well dead, but this forensic style of debugging has been replaced with the interactive real-time diagnostics tools of the Debugger—in particular, the Trace Window. Also, as we mention in the Reference section, dumping this file can slow down performance to a crawl. Skip it. Check out Event Tracking or the Coverage Profiler for newer, better, faster ways to do what you want.
SORT
Same deal as INSERT
and JOIN
. Fast and efficient indexing
makes the physical order of the database almost irrelevant. If you must create
a table in a particular order for output, consider using SQL SELECT
to generate
the table. Using a Rushmore-optimizable query, output will be far faster, and
much less disk space will be consumed.
SYS(12)
, SYS(23)
, SYS(24)
, SYS(1001)
These functions provide a suite of memory-reporting functions. In MS-DOS days gone by, we needed to check to make sure that EMS memory was allocated and available, that we had the room to create our objects, and that we weren’t going to crash on creating the next object. Now, with the Win32 virtual memory system, this is far less likely, even on marginal machines, and these functions can, on the whole, be ignored.
UPDATE
UPDATE
(not the SQL version, the Xbase one) is a close
relative of the JOIN
command, and works with a similar logic. This command
updates the contents of one table based on the contents of a second. The logic
is a bit loopy, and should your orders or indexes not match, really bad things
can happen. There are too many commands that will let you avoid attempting this
nightmare function to list them all, but let’s give you an idea of a few. If
you’re updating multiple records interactively, set table buffering to update
them all at once. Try SCATTER
and GATHER
and their cool ARRAY
keyword to batch
a group of records programmatically. Update from a cursor with a SCAN...ENDSCAN
logical structure. See? Many workarounds exist and there’s no need to use (or
even to try to understand) this old behemoth of a command.
Despite the section heading, a few of these might even belong in test code, not just in the Command Window. But, for sure, none of them belongs in an application.
These commands don’t have as many poor side effects as the killers listed above, and sometimes they can speed the development process. As developers, we should be able to understand their bad or unintended side effects, and use them only in appropriate circumstances.
VALIDATE DATABASE RECOVER
This is an indispensable command for fixing database containers gone bad. Of course, our idea of “fixing” is somewhat different than the Fox team’s—instead of repairing the bad parts, this command cuts them out of the DBC (with your permission, of course). But it’s a start to a process of fixing up problems.
Prior to VFP 7, there was no way to use VALIDATE DATABASE
RECOVER
anywhere but from the Command Window (not even in a PRG in a
development environment). VFP 7 added the capability to use this command in a
runtime environment. However, we don’t think this is something you should ever
do in an application. It displays dialogs with messages such as “Object #7
(Table ‘orditems’): The fields in table ‘d:\myapplication\data\orditems.dbf’
did not match the entries in the database. Would you like to delete this object
or cancel the validation?” Not exactly the kind of question we like to ask
our users! And do you really think things will be better when they choose
either one of those options? (Doug likes to think of “cancel” as
“leave me hosed” and “delete” as “hose me worse than I
already am.”)
The best way to fix a client’s DBC is to restore it from
backup or send them your copy. (After all, while the contents of their tables
will obviously differ from yours, the DBC itself shouldn’t.) VALIDATE DATABASE
RECOVER
should be a tool you use to fix your database.
APPEND
APPEND
was intended primarily as an interactive command.
APPEND
gives you a raw view of a file, suitable for dumping data into a system.
If you’re testing from the command line and you just need to pop one record
into the table with a negative balance, this is an easy way to do it. But this
is not an end-user interface. It gives raw field names, no help on the status
bar, no tooltips, and worst of all, no application logic (“save this
record only if…”). APPEND
is good for quick-and-dirty data entry by a
programmer; it’s unsuitable for end users.
BROWSE
/EDIT
/CHANGE
Interactively, these are a fast way to change your data on the fly and get back to troubleshooting. Database container rules and triggers keep you from shooting yourself in the foot too badly, but you don’t have to endure the overhead of starting the entire app, logging in and getting going if all you want to do is test a routine for one condition in the data.
On the other hand, the same capabilities that make these
functions attractive to us can make them a killer in the hands of an end-user.
BROWSE
is not really an easily trappable part of the event loop (though we know
many developers who have made it work), meaning that “On Selection”
and “Upon Leaving” events are difficult to fire with reliability.
Sequencing of data entry is not enforced—you can jump all over the BROWSE
fields if you’d like.
These commands provide too many capabilities and too little control to hand to our end-users. Use forms and grids in your application instead.
CREATE
We find it astounding that the Table Designer is available in applications distributed with the FoxPro runtime files. What on earth are they thinking up there in Redmond?
If your users want to be able to CREATE TABLE
s on their own,
guide them through their choices with your own dialogs (perhaps even a custom
Wizard), then use one of the CREATE
commands to make the table. Check to make
sure they’re not overwriting the main tables of your application—you can bet
they won’t check!
FIND
FIND
was also intended more for interactive use, and is
included “for backward compatibility.” It locates the first record
whose index value matches the parameter passed with the FIND
command. It’s
really nice to be able to type FIND GATES
in the Command Window, but use SEEK
or LOCATE
instead in your apps.
MODIFY STRUCTURE
This is as dumb as the CREATE
command above. Why on earth do
you think your users would be interested in learning all the rules of good
table- and field-naming conventions? Don’t include it in your apps. If tables
need to be changed on the fly, use the ALTER TABLE
command instead.
The commands in this final set are dangerous but may occasionally be used in an application, as long as precautions are taken to avoid the potential disasters that can occur.
ZAP
While it’s the fastest way to blow away all the records in a
table, one big problem is that ZAP
fails to fire the DELETE TRIGGER
of tables
contained within a database. With SET CONFIRM OFF
, ZAP
, to paraphrase the
immortal words of a sneaker commercial, just does it, with no confirming
dialog. There is no “undo” within the language to recover the lost
records.
So what’s wrong with using ZAP
when you know what it is you
mean to do? There are too many scenarios where some event can trigger a change
to the current work area in such a way that ZAP
can blow away your hard-earned
data. For example, an ON KEY LABEL
routine that changes work areas as part of
its processing, and sloppily fails to change it back, can work under most circumstances
within your system. If most of your data-manipulation routines check to make
sure they are in the right area before performing their tasks, the OKL will
probably work fine and could go undetected for years. But, if that OKL fires
between the lines of code (ON KEY LABEL
s can occur between any two lines of
code), say between SELECT TEMP
and ZAP
, well, we hope you have made good
backups. Starting in VFP 7, you can use ZAP IN
to ensure the correct work area
is zapped, so this problem has gone away.
The one place we think ZAP
belongs in a production
application is to empty cursors that hold temporary data, especially those
bound to a grid, since you can’t just re-create the cursor in that case.
PACK
PACK
works by copying all records not marked for deletion to
a temporary file. It then renames the old file to a new name, the new file to
the original name, blows away the old file, and, if the table belongs to a
database container, fixes the header of the new file so it properly points to
the DBC.
Some gurus maintain that there exists a critical portion of
time between the first rename and the second when a catastrophic system failure
(such as the complete loss of power) could leave us with no data files
whatsoever. Since in fact we’ve never heard a single person ever say they lost
their data by packing (the window for failure is extremely small) and since
issuing the equivalent commands ourselves essentially duplicates the internal
process using slower VFP code instead of faster C code, we think this belongs in
the realm of “urban myth.” However, we feel due caution is wise,
considering how painful such a loss could be. You might want to squirrel away a
copy of the table just before issuing PACK
so if something horrible occurs, you
can get the data back.