Logo

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.

Commands Never to Use

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.

Never, Ever Use These Commands:

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.

Commands for Development, Never for Production

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 TABLEs 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.

Never Say “Never”: Use With Caution

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 LABELs 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.