04.08.2013 Views

Printing - FoxTalk - dFPUG-Portal

Printing - FoxTalk - dFPUG-Portal

Printing - FoxTalk - dFPUG-Portal

SHOW MORE
SHOW LESS

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

September 1996<br />

3.0 or Not 3.0? That's the Question!<br />

Art Bergquist<br />

How do you tell if a table is a Visual FoxPro table or not? That's easy! You display the value of<br />

SYS(2029) to return either a 48 (for a "Visual FoxPro table with or without a memo field"<br />

message) or some other number (see VFP's online help for other possible values).<br />

What if you want to determine if a table is a Visual FoxPro table but the table isn't open?<br />

SYS(2029) returns a 0 ("No table open") in that case, so that isn't very helpful. You could open<br />

the table first, but what if you need to know without first opening the table.<br />

A technical way of doing this is to go to MS-DOS and use DEBUG to inspect the table's contents<br />

(at the DEBUG dash (-) prompt type 'd100' and then type 'q' to Quit). A Visual FoxPro table has<br />

a character zero (0) in the first byte (byte 0). Non-VFP tables (and other DOS files in general, for<br />

that matter) have a non-zero value in their first byte.<br />

The easier way is to use the following function (I've aptly named it VFPTABLE), which simply<br />

reads the first byte (byte 0) and returns .T. if it's a character zero (0) and .F. if isn't. The function<br />

thoroughly validates the input parameters by ensuring that a non-empty character string is<br />

passed, that this string represents a DOS file that actually exists, and, finally, that the file can be<br />

opened (in testing VFPTABLE from the Visual FoxPro Command window, for example,<br />

C:\VFP\VFP.EXE couldn't be opened by FOPEN):<br />

(1)


* Program Name: VFPTABLE.PRG<br />

* Author: Art Bergquist<br />

* Date: May 11, 1996<br />

* Purpose: Determine if the specified file is a Visual<br />

* FoxPro table or not<br />

* Syntax: m.lIsVFP = vfptable("mytable.dbf")<br />

* Input: tcFileName = Name of the DOS file to check<br />

* Output: .T. = the DOS file is a Visual FoxPro table<br />

* .F. = the DOS file is NOT a Visual FoxPro table<br />

PARAMETERS tcFileName<br />

#INCLUDE foxpro.h<br />

PRIVATE ALL LIKE l_*<br />

* name of the currently executing program<br />

l_Program = PROGRAM()<br />

DO CASE<br />

CASE PARAMETERS() = 0<br />

DO ErrorMsg WITH l_Program, ;<br />

'You must supply a filename.'<br />

RETURN .F.<br />

CASE TYPE('tcFileName') # 'C'<br />

DO ErrorMsg WITH l_Program, ;<br />

'You must supply a *character* filename.'<br />

RETURN .F.<br />

CASE EMPTY(tcFileName)<br />

DO ErrorMsg WITH l_Program, ;<br />

'You must supply a non-empty filename.'<br />

RETURN .F.<br />

CASE NOT FILE(tcFileName)<br />

DO ErrorMsg WITH l_Program, ;<br />

UPPER(tcFileName) + ' does not exist!'<br />

RETURN .F.<br />

OTHERWISE<br />

l_FileName = UPPER(tcFileName)<br />

ENDCASE<br />

* open the file (in read/only mode)<br />

l_FileHandle = FOPEN(l_FileName, 0)<br />

IF l_FileHandle = -1<br />

DO ErrorMsg WITH l_Program, ;<br />

l_FileName + ' could not be opened (by FOPEN).'<br />

RETURN .F.<br />

ENDIF<br />

* Read the first byte<br />

l_FirstByte = FREAD(l_FileHandle, 1)<br />

* close the first file<br />

= FCLOSE(l_FileHandle)<br />

* First Byte = '0' if it's a Visual FoxPro table<br />

* non-0 if it's not a VFP table.<br />

l R t (l Fi tB t '0')


In what situation would you need to use such a function? I'm glad you asked. The following<br />

program can be used to import/add FoxPro tables (.DBFs) to a Visual FoxPro database (.DBC).<br />

If the tables you are adding are Visual FoxPro free tables, then no problem. If any table is a<br />

FoxPro 2.x-style table, however, a dialog box with the following message will appear:<br />

File '.DBF' will be updated to a new file format,<br />

making it unreadable to previous versions of FoxPro.<br />

This can be reversed with the 'COPY TO' command, and the<br />

old table will be saved as a .BAK file. Continue?<br />

> < No ><br />

If you don't mind running this program interactively (in other words, answering Yes anytime the<br />

program is about to add a FoxPro 2.x-style table), then you don't need to use the VFPTABLE<br />

function. If, however, you would like to run the program in a batch, unattended mode (for<br />

example, if you were looping through a list of .DBF files in a directory and calling<br />

ADD2DBC.PRG for each .DBF file), use a program like ADD2DBC.PRG:<br />

* Program Name: ADD2DBC.PRG<br />

*<br />

* Auto-answer the 'Table Update Warning' message<br />

* that comes up when you add a FoxPro 2.x-style<br />

* table to a VFP 3.0 database:<br />

*<br />

* File '.dbf' will be updated to a new<br />

* file format, making it unreadable to previous<br />

* versions of FoxPro. This can be reversed with<br />

* the 'COPY TO' command, and the old table will<br />

* be saved as a .BAK file. Continue?<br />

*<br />

* <br />

* - -<br />

PARAMETERS tcFileName<br />

IF NOT VfpTable(tcFileName)<br />

* Auto-answer the 'Table Update Warning' message<br />

KEYBOARD 'Y' PLAIN<br />

ENDIF<br />

ADD TABLE (tcFileName)<br />

* Auto-erase the .BAK file that was created<br />

ERASE (tcFileName + '.BAK')<br />

As you deal with the complications brought on by having to deal with two different types of<br />

files2.x and 3.0 tables -- that have the same extension, it's useful to have tools on hand that make<br />

short work of some of the problems you run into. This technique will help with a couple of those


problems.<br />

Art Bergquist can be reached at 73762,3566@compuserve.com.<br />

Visual FoxPro 5.0: A Sneak Preview<br />

Whil Hentzen<br />

The next version of Visual FoxPro has been in beta testing for a number of months. I'm finally<br />

allowed to talk about it, so this month I'll give you a sneak preview of what you'll be using in a<br />

few months.<br />

Most <strong>FoxTalk</strong> readers won't get their hands on the new release for a few more months, so I'm not<br />

going to spend this entire issue flooding you with information that you can't use for a while.<br />

Nonetheless, you're curious and want information that can help you plan for the future, so I'll do<br />

a preview in this month's editorial and then cover these new features over the next few months -and<br />

when Visual FoxPro 5.0 hits the street, we'll get more detailed.<br />

First of all, you might be asking, "5.0? Did I fall asleep? What happened to 4.0?" Nope, the<br />

version of VFP that follows 3.0 is going to be named 5.0 -- just as Microsoft Word jumped from<br />

version 2.0 to 6.0 a couple years ago. Going from 3.0 to 5.0 will keep VFP's numbering in sync<br />

with the next releases of Visual Basic 5.0 and Visual C++ 5.0 (both of which are due out "real<br />

soon now"). That, of course, brings to mind two questions: "First, why is it important to keep<br />

version numbers in sync?" And, from the more thoughtful of you: "Will 5.0 suck up significantly<br />

more resources like Word did when it went to version 6.0 from 2.0?"<br />

Let's address the second question first: No, 5.0 won't require more resources -- in fact, one of the<br />

primary design goals for version 5 is improved performance -- especially in areas that affect the<br />

user interface, such as form instantiation, and in situations with 8M and 12M of RAM as well.<br />

As far as the version numbers are concerned, remember that all three tools, Visual C, Visual<br />

Basic, and Visual FoxPro, are going to be merged into a common development environment<br />

called the Developer's Studio. We can look forward to concurrent releases of these products in<br />

the future, so it will be easier to keep them straight if they're numbered similarly. Furthermore,<br />

as each of these tools begins to use the same components, such as the Debugger or Report<br />

Writer, it will again be easier to keep the revisions straight.<br />

Now to the $64,000 question -- what's new in VFP 5.0? The fundamental goal of version 5.0 is to<br />

provide the developer tools that we need in order to keep up with the amazing improvements in<br />

the FoxPro language. When you fire up 5.0, the first thing you'll see is that the editor has been<br />

colorized -- keywords, variables, comments, and the like all have their own colors -- and yes,<br />

you can tailor those colors to suit your preferences. If you think quickly, the second thing you'll<br />

notice is that by right-clicking you'll get context menu that extends the capabilities of the editor


-- the ones I like the most are the ability to clear the Command window and the ability to execute<br />

the highlighted expression.<br />

Touring around the interface some more will bring you to the Debugger, which is a whole new<br />

animal. If you've seen the Debugger in Visual Basic or Visual C++, you'll be reasonably at<br />

home. It's a completely separate process from Visual FoxPro, which means that it's a bit<br />

disconcerting to see the entire window disappear behind the FoxPro window. Then you'll realize<br />

that this means that using the Debugger is cleaner now, since it doesn't participate in any FoxPro<br />

events.<br />

The Debug window actually has five pieces: Trace, Watch, Locals, Call Stack, and Output. You<br />

can select which of these components are displayed, and they can all be positioned at will. You'll<br />

soon find that the 17-inch monitor that you've been eyeing is now a requirement -- and an even<br />

larger one might not be that bad of an idea. Besides the obvious capabilities that these windows<br />

give you, there are three more functions that I really like. Event Tracking allows you to send the<br />

results of events you specify (such as particular methods) either to the Output window or to a<br />

text file. Coverage logging allows you to see what code is being executed. You can also save a<br />

particular Debugging configuration to a file to save time when setting up your development<br />

environment. (Remember what a pain it was, having to type in variable and variable in Debug,<br />

only to lose them if you had to exit out of FoxPro?) OK, and now that your appetite's been<br />

whetted, we now have assertions in the language!<br />

The next great feature that everyone wanted has to do with the way that classes are handled. For<br />

instance, if you drag fields from the Data Environment to a form, you've been stuck with the<br />

Visual FoxPro base classes -- there hasn't been a way, short of writing a builder, to tell VFP to<br />

use your own classes instead. In version 5.0, however, the new default class and library<br />

properties will allow you to assign a control type to a field: when you add the field to a form,<br />

you'll create the exact control you want in one easy step.<br />

The database container has been enhanced as well. For example, it will allow multiple users to<br />

simultaneously create and modify objects within the same database, and the engine now supports<br />

changes to values that violate rules. Both changes will solve a lot of headaches for you.<br />

Probably the most requested feature for this version of Visual FoxPro has been outer joins, and<br />

you won't be disappointed. You can create a variety of outer joins, queries that return "Top N"<br />

results, and alias columns, and you can do this all in the Query and View Designers to boot.<br />

The Form Designer has been enhanced as well. The Properties window has a ton of new<br />

interface capabilities, including Property Zoom and keyboard navigation. There are additional<br />

properties, events, and methods with many controls, and there's a whole slew of new OLE<br />

controls as well. Other interface goodies include the ability to flip back and forth between Run<br />

and Design modes with a click on the toolbar, the ability to manipulate multiple controls at one<br />

time, and additional alignment capabilities.<br />

You'll now be able to create SDI applications so that your application windows can be children<br />

of the Windows desktop. And you'll be able to create context menus with the Shortcut Menu<br />

Designer (your controls will be able to respond correctly to their new RightClick event). And for


those of you who've completely immersed themselves into the Windows environment, you can<br />

create OLE servers with Visual FoxPro and other applications will be able to query FoxPro just<br />

as FoxPro has been able to access other applications such as Word and Excel.<br />

Remember that this was just a brief tour. I could have spent several pages (instead of one<br />

paragraph) on the Debugger alone -- but it should give you a taste of what's going to be in your<br />

hands in short order. You think VFP 3.0 was exciting? With 5.0, it'll just get better.<br />

An Easier Way to Make Configuration Files<br />

with GetConfig()<br />

Bill Budney (2)<br />

Most applications require some configuration information such as paths to files, information about<br />

a user, whether you are running in development mode or as a user, optional features, and so on. If<br />

you're like me, you've probably been using either GetEnv() to query DOS environment variables or<br />

a configuration table in your application. However, I find both of these techniques annoying at<br />

times, so recently I started using a third alternative that I'd like to share.<br />

Several things bug me about GetEnv(). First, I have to reboot to change an environment variable.<br />

If I'm testing or debugging an application, I sometimes want to flip back and forth between<br />

Development mode and User mode. If my application tests for something like GetEnv("User")<br />

="DEVELOPMENT", then I have to reboot to make this change. Another annoyance is that I<br />

have to remember to set the environment variable on every user's PC. Even on a LAN, user's<br />

boot drives frequently aren't published on the network, so these edits either have to be made by<br />

the user or by going to each PC and editing AUTOEXEC.BAT, unless the system manager has<br />

set up a LAN-based startup script. If some program or well-intentioned system manager disables<br />

the settings in AUTOEXEC.BAT for some reason unrelated to my program, it could break my<br />

application.<br />

A configuration table can be modified without rebooting the computer, but it requires a<br />

development version of FoxPro (and a user comfortable enough with BROWSE to make the<br />

necessary modifications), or a dedicated maintenance form to modify the table. The problem<br />

with a dedicated form is that some setup options might be required just to run the program, but if<br />

the options are set incorrectly, the user might not be able to get to the maintenance form to<br />

change it. And, although BROWSE isn't particularly difficult, some users can't be trusted with it.<br />

Finally, some clients don't even have a development version of FoxPro handy, so they couldn't<br />

make the changes even if they knew how.<br />

CONFIG.FPW<br />

The solution that I like the most is to use a text-based configuration file. Some of my apps have<br />

an application-specific .INI file, which I can read or write to using the Windows<br />

GetPrivateProfileString and WritePrivateProfileString API functions. However, FoxPro already


equires a text-based configuration file (CONFIG.FPW), so why not put my setup information<br />

there? I can use either a LAN-wide default CONFIG.FPW or let each user have their own. Often<br />

I do both -- using a default CONFIG.FPW on the LAN, while letting users override the defaults<br />

with their own CONFIG.FPW.<br />

GetPrivateProfileString is easy to use on .INI files. The trick to using it with CONFIG.FPW is to<br />

add a section header. FoxPro will ignore lines that it doesn't understand, so you can safely add<br />

lines to CONFIG.FPW without generating an error. I use a single section called [Common], but<br />

you can use any section name you want, or you could get fancy with additional sections. (By the<br />

way, you can modify the current CONFIG.FPW quickly with the command MODIFY<br />

COMMAND sys(2019) ).<br />

A sample CONFIG.FPW might look like this:<br />

* CONFIG.FPW<br />

talk = on<br />

safety = on<br />

exclusive = off<br />

resource = C:\foxuser<br />

[Common]<br />

PATH = c:\vcommon;c:\common<br />

User = Development<br />

Compare = c:\nu\fcompare.exe<br />

Default = C:\Dev\LT2<br />

_GENMENU = 'c:\vcommon\GENMENUX.PRG'<br />

command =do c:\vcommon\bbStart.prg<br />

Calling GetConfig()<br />

Using GetConfig(), I can find the values for any of the "keys" in the [Common] section. (A key<br />

is the value to the left of the equals sign, while its value is the text to the right of the equals sign.)<br />

GetConfig always returns values in uppercase, so you can count on that when you test values:<br />

lcPath = GetConfig("Path")<br />

Here's another example:<br />

if GetConfig("User") = "DEVELOPMENT"<br />

*-- Turn on Developer menu pad or other features here<br />

endif<br />

How it works


GetConfig is a special case of calling GetPrivateProfileString. GetConfig assumes that the file<br />

you are reading is the current Config.FPW, and that all of your keys are in the [Common]<br />

section. Three lines of code are at the core of GetConfig:<br />

lnHandle = RegFn("GetPrivateProfileString", ;<br />

"CCC@CIC", "I")<br />

lcRetVal = SPACE(200)<br />

=CallFn(lnHandle, "Common", tcKey, "BogusValue",;<br />

@lcRetVal, LEN(lcRetVal), sys(2019))<br />

The RegFn call registers the GetPrivateProfileString API function. If you look up<br />

GetPrivateProfileString in C:\VFP\WIN32API.HLP, you can get a clue about the calling<br />

parameters. I've found that converting the documentation (written for C programmers) into<br />

FoxPro code isn't difficult, although it can be a bit confusing until you get the hang of it.<br />

The second line creates a memory variable with sufficient space to store the results from the API<br />

call. This is a necessary step when calling some Windows API functions. If you pass a variable<br />

by reference, you must initialize the variable first. This is because C (which is the language that<br />

the API function is written in) requires variables to be initialized before use, and it can't change<br />

the size of the variable on the fly as FoxPro can.<br />

Finally, the CallFn line makes the actual call to GetPrivateProfileString. Table 1 lists the<br />

parameters passed.<br />

Table 1. The parameters that can be passed to GetPrivateProfileString.<br />

lnHandle The "handle" returned by RegFn.<br />

"Common" Hardcoded because GetConfig assumes you are lookingat the [Common] section.<br />

tcKey The key to search, as passed to GetConfig. Ex: GetConfig("User").<br />

"BogusValue" A seed for the key value, in case the value is emptyor the key is missing.<br />

@lcRetVal The variable (passed by reference, as a "pointer")where the results will be stored.<br />

LEN(lcRetVal) The size of lcRetVal (required by the API).<br />

sys(2019) The current Config.FPW in use.<br />

GetPrivateProfileString returns lcRetVal as a null-terminated string. A null is an ASCII 0. To<br />

make it a suitable FoxPro character variable, simply strip out the null. That's it!<br />

The entire program looks like this:


*FUNCTION GetConfig<br />

* Abstract...: Reads info from the current CONFIG.FPW.<br />

* : Requires a [Common] "section" marker in<br />

* : Config.fpw similar to .INI files.<br />

* : For example:<br />

* :<br />

* : [Common]<br />

* : Path = MyPath;YourPath<br />

* :<br />

* Parameters.: tcKey is the key to read<br />

* : ("Path") in the above example.<br />

* : Case insensitive.<br />

* Returns....: lcRetVal<br />

* Author.....: Bill Budney<br />

* Copyright..: 1995-1886, BBA Technologies<br />

* Date.......: May 18, 1995<br />

PARAMETERS tcKey<br />

PRIVATE lcRetVal, tcKey<br />

IF NOT "FOXTOOLS" $ UPPER(SET("library"))<br />

SET LIBRARY TO home() + "foxtools" ADDITIVE<br />

ENDIF<br />

*-- Register the GetPrivateProfileString API.<br />

lnHandle = RegFn("GetPrivateProfileString", ;<br />

"CCC@CIC", "I")<br />

*-- Make some space for the return value.<br />

lcRetVal = SPACE(200)<br />

*-- Call the API.<br />

=CallFn(lnHandle, "Common", tcKey, "BogusValue", ;<br />

@lcRetVal, LEN(lcRetVal), sys(2019))<br />

*-- Remove the nulls.<br />

lcRetVal = TRIM(STRTRAN(lcRetVal, CHR(0), ""))<br />

IF lcRetVal = "BogusValue"<br />

lcRetVal = ""<br />

ENDIF<br />

RETURN Upper(lcRetVal)<br />

Summary<br />

GetConfig is a quick and easy way to read configuration information from CONFIG.FPW.<br />

GetConfig doesn't require rebooting, global variables, or custom _Screen properties. It doesn't<br />

even require a copy of FoxPro to visually inspect or modify the file. The result is fewer<br />

configuration files to worry about and a quick and easy way to poll for setup information.<br />

GetConfig will work with all versions of Windows and all versions of FoxPro for Windows and<br />

Visual FoxPro. If you're using FoxPro for DOS or Mac, then you'll need to seek alternative


solutions.<br />

Bill Budney is located at BBA Technologies, 1401 Beacon St., Suite 206, Brookline, MA 02146.<br />

617-730-9798, budney@bbatech.com.<br />

Create an Automated Development Log<br />

Doug Hennig<br />

Logging programming changes has helped the staff at Stonefield Systems Group in a lot of ways,<br />

including systems documentation and tracking. It also serves as the foundation for an automated<br />

system of generating incremental distribution files when a new version of an application is released.<br />

This month's column provides an automated logging tool that can help you manage these issues.<br />

More than a decade ago, I realized that the way I was managing software development wasn't<br />

working. I'd get a support call from a client, spend a long time trying to figure out why the client<br />

got an error message but my version ran fine, only to discover that several program components<br />

(I like to use the generic term "modules") on the client's system weren't the same as the ones on<br />

mine. Sometimes this was caused by my not installing all the pieces at the client site when a new<br />

version was released, sometimes it was because the client didn't install the new version I sent<br />

them. Either way, I couldn't just install my copy of the modules on their system; what if those<br />

modules impacted other pieces of the program or what if they expected that other things (like<br />

table structures) had changed? I needed to know exactly what differences there were between the<br />

version the client had and the one I had.<br />

If you're like me, you're so busy you can hardly remember what you did last week let alone the<br />

details of what changes you made months ago. I needed a better way to determine what changes<br />

had been made to each module in an application and when those changes were made. I decided<br />

to log every change I made to every component in every application I've created since then.<br />

Initially, I logged the changes in a Development Worksheet form, but later I decided to create a<br />

FoxPro application and log the changes to a table, because it made searching for a specific<br />

module (especially library routines used in many applications) easier. Our log files at Stonefield<br />

now have more than 35,000 records.<br />

In this month's column we'll look at a development logging system we use at Stonefield to record<br />

what changes were made, when they were made, and to what modules in the applications and<br />

tools we develop. Since we started using this little tool, it has saved us on numerous occasions<br />

and also serves as the basis of an automated system of delivering updates to people who<br />

purchase our tools.<br />

(3)


Advantages of logging changes<br />

We've found that logging changes made to modules gives us a lot of advantages:<br />

• The documentation for all changes made to all modules in an application are in one place.<br />

You don't have to scan through a dozen program files looking for change comments.<br />

• Changes made to library routines can be examined when an older application using those<br />

routines needs to be maintained. In the past, we found that changing a library routine<br />

occasionally broke applications we hadn't looked at in some time after the application<br />

was rebuilt using the new library routines. The problem, of course, is that the only way to<br />

find out which parts of the application may have been affected is to retest everything.<br />

Using the development log, you can quickly check which library routines have changed<br />

since the application was released and concentrate on what impact those changes may<br />

have rather than worrying about every library routine.<br />

• What if a client didn't install the last update you sent them? This is especially problematic<br />

if you have many customers using the application over a wide area. For example,<br />

Stonefield maintains a retail pharmacy application called Pharmacy Partner that's used<br />

by pharmacies all over two Canadian provinces. It isn't unheard of for a pharmacy to not<br />

install an update we send to them. When it comes time to install yet another update or to<br />

answer a support call, it's usually important to know what differences there are between<br />

older and newer versions. Having a log of changes allows you to go over the incremental<br />

differences between the versions and prepare to handle any issues that arise because of<br />

these differences.<br />

• In his book Code Complete, Steve McConnell says that frequently changed routines or<br />

those previously found to be error prone are the most likely to continue to contain errors.<br />

By examining the history of the modules in an application, you can determine which<br />

modules have been edited the most, and target your efforts at cleaning those up or even<br />

completely rewriting them to eliminate the problems.<br />

• In my experience, if something that used to work stops working, it's usually not because a<br />

file got corrupted but rather because something was changed. The most likely culprit is a<br />

recently changed program. By looking at the development log, you can determine which<br />

modules were changed and when. The problem is likely related to one of the most recent<br />

changed modules.<br />

• In a multi-developer or multi-project environment, a project manager can tell what others<br />

have done on a project, which bugs have been fixed, which enhancements are finished,<br />

and so forth. Frequently I and other Stonefield developers are working on several projects<br />

at once, and the development log is another tool that helps me keep track of where I and<br />

the others are at in each one.<br />

The big payoff: incremental distribution<br />

As important as logging module changes has been to us, we recently discovered an even more


useful benefit: automating incremental distribution.<br />

As a tool vendor, we're constantly maintaining our products: adding new features, correcting<br />

defects, and so forth. The problem: how do you distribute these changes (especially bug fixes) on<br />

a timely and cost-effective basis to a large installed base? Until recently, we would send out a<br />

"maintenance release" on a floppy disk, but this gets expensive, takes time to coordinate and<br />

distribute, often doesn't get the changes out to customers as quickly as they're made, and can<br />

cause a lot of headaches (if even five percent of the disks arrive unreadable, each of those<br />

customers is going to phone and we'll have to send them a replacement disk). We thought about<br />

making the new release available on our Web site, but since a complete product barely fits on a<br />

disk even after it is compressed, that would make for large files to download. Also, how do you<br />

prevent people who didn't buy your product from downloading the complete product and getting<br />

it for free?<br />

The solution we came up with was to electronically distribute an incremental set of changes.<br />

This keeps the download file small and allows us to post a new release on our Web site as often<br />

as changes are made. The incremental changes are built into a FoxPro APP that checks for the<br />

existence of a key file that's part of the original product to ensure the update is being installed by<br />

a legitimate customer. If it can't find the file, it displays an error message and terminates.<br />

Otherwise, it copies the incremental changes over the original files and updates the product to<br />

the latest version.<br />

The only downside to this process is managing the creation of the incremental file. How do we<br />

track which changes were made from one release to the next so we can ensure all changes are<br />

distributed? Wait a second, isn't that what the development log keeps track of? What if we could<br />

create an automated routine that reads the development log file, picks up the name of all modules<br />

changed in a given release, and then packages them for distribution? We'll see how two routines,<br />

MAKEUPD and INSTUPD, do just that.<br />

Using the development log<br />

The development log tool, DEVLOG, is provided in download file _HENNIG.EXE in both<br />

Visual FoxPro and FoxPro 2.x (cross-platform DOS and Windows) versions. When you run the<br />

appropriate version of DEVLOG.APP, it installs itself in the FoxPro system menu bar (under the<br />

Tools pad in the case of the VFP version and under the Program pad in the case of FoxPro 2.x),<br />

so it's quickly available when you need it. I run DEVLOG.APP from my FoxPro startup program<br />

(VFPSTART.PRG in the case of VFP and FOXSTART.PRG in the case of FoxPro 2.x), so it's<br />

installed automatically whenever I start up FoxPro.<br />

Figure 1 shows the VFP version of DEVLOG (the user interface for the FoxPro 2.x version is a<br />

little different but the functionality is similar). To define the applications you're working on in<br />

DEVLOG, choose the Application page, click on the New button, and enter the name of the<br />

application and a shorthand code for it. You can also delete applications in this page; doing so<br />

also deletes all log entries for that application, so you're prompted to ensure you want to do that.<br />

The development log page is where you make log entries. Select the name of the application<br />

from the Application drop-down list. Then enter the version number of the application and the


date (the default is the current date, or the date previously entered; this is handy when you are<br />

entering batches of records at a time). Next, either enter the name of the module (.PRG, .SCX,<br />

.FRX, and so on) or choose it from the existing entries in the Module combo box (when you<br />

select the application, the Module combo is populated with all modules from existing log entries<br />

for this application). Enter a description of the changes made in the Description edit box and<br />

choose the Save button. The Module and Description controls clear, ready for you to enter the<br />

next log entry for the same application, version, and date.<br />

You can navigate through log entries on the List page. If you wish to edit one of the entries, click<br />

on the Edit button to activate the first page and place the selected entry in it. If you wish to add a<br />

new entry without saving the selected one, click on the New button.<br />

A closer look at the development log<br />

Unfortunately, the code for DEVLOG, MAKEUPD, and INSTUPD is too long to publish here<br />

(it's available in download file _HENNIG.EXE), but we'll look at a few interesting features of<br />

these routines.<br />

DEVLOG.PRG is a program used by both the VFP and FoxPro 2.x versions of the development<br />

log as the main program in the application. This means it must handle differences between these<br />

versions. For example, while LOCAL is a valid command in VFP, it gives a compile error in<br />

FoxPro 2.x, so DEVLOG.PRG uses PRIVATE instead.<br />

If DEVLOG.APP is called without any parameters, it assumes this is an "installation" call, so<br />

DEVLOG.PRG adds two new bars to the appropriate pad in the system menu bar (under Tools<br />

for VFP and Program for FoxPro 2.x). The first bar is simply a separator line while the second is<br />

"Development Log." Of course, if DEVLOG.APP has been run before, we don't want to add it to<br />

the menu again, so we must handle that case:


lnBar = 0<br />

lcPrompt = 'De\


lcDirectory = substr(sys(16), ;<br />

rat(' ', sys(16)) + 1)<br />

lcDirectory = left(lcDirectory, ;<br />

rat('\', lcDirectory))<br />

on selection bar lnBar + 1 of &lcPad ;<br />

do locfile('&lcDirectory.DEVLOG.APP', 'APP', ;<br />

'Locate DEVLOG.APP') with 'Run'<br />

set sysmenu save<br />

Notice the DO command passes a string to DEVLOG.APP. This is so when it's called from the<br />

menu, it isn't an "installation" call. The string passed is unimportant, but I set it to "Run" so it's<br />

obvious what we're doing. Also notice SET SYSMENU SAVE is used so if you later use SET<br />

SYSMENU TO DEFAULT, Development Log is still available in the menu.<br />

DEVLOG.PRG creates the tables it needs (DEVLOG.DBF, DEVAPPL.DBF, and<br />

DIRECTRY.DBF) if they don't exist. However, in VFP, we need to ensure that these tables<br />

aren't inadvertently added to any open database, so we need to use the FREE keyword in the<br />

CREATE TABLE command. Since this would cause an error in FoxPro 2.x, we create a variable<br />

called lcFree that either contains the word FREEor a blank string, depending on the version, and<br />

macro expand it in the CREATE TABLE statement. Thus, in VFP the effective command is<br />

CREATE TABLE FREE, while in FoxPro 2.x it's CREATE TABLE. To ensure that these tables<br />

are created in the same directory as DEVLOG.APP, which may not be the current directory, we<br />

use the same lcDirectory variable defined earlier as the directory where the tables should be<br />

located:<br />

lcFree = iif('Visual' $ version(), 'free', '')<br />

create table (lcDirectory + 'DEVLOG') &lcFree ;<br />

(APPLIC C(10), ;<br />

VERSION C(4), ;<br />

DATE D, ;<br />

MODULE C(12), ;<br />

DESCRIP M)<br />

If DEVLOG.APP isn't called as an "installation" call (in other words, you selected it from the<br />

menu), it needs to run the appropriate routine: DEVLOG.SPR in the case of FoxPro 2.x or the<br />

DEVLOG form in the case of Visual FoxPro. Since these two routines are called differently,<br />

DEVLOG.PRG must handle it:<br />

lcRun = iif('Visual' $ version(), ;<br />

'form DEVLOG', 'DEVLOG.SPR')<br />

do &lcRun with lcDirectory<br />

In the VFP version of the DEVLOG form, we want to enable only the Save button when all the<br />

necessary information has been entered. The following code was placed in the


InteractiveChange() method of each control:<br />

This.Parent.cmdSave.Refresh()<br />

This.Parent is used instead of Thisform because the Save button is sitting on the same page in a<br />

pageframe as the other controls. The Refresh() method of the Save button has the following<br />

code:<br />

with This.Parent<br />

This.Enabled = not empty(.cboApplication.Value) and ;<br />

not empty(.txtDate.Value) and ;<br />

not empty(.cboModule.DisplayValue) and ;<br />

not empty(.edtDescription.Value)<br />

endwith<br />

This ensures that as soon as the user starts typing something in the last field needing to be filled<br />

in, the Save button is enabled. Putting the call to cmdSave.Refresh() in the Valid() method of<br />

each control instead of InteractiveChange() would result in the Save button staying disabled until<br />

you try to exit the control.<br />

Because FoxPro 2.x doesn't easily support BROWSE windows integrated with READ windows,<br />

the list of log entries is implemented as a "pick list" routine instead. When you choose the Find<br />

button, you're asked to select a module. Doing so then brings up a BROWSE-based pick list of<br />

all log entries for that module. Highlighting one record and pressing Enter brings that record up<br />

for reading or editing.<br />

MAKEUPD and INSTUPD: Incremental Distribution<br />

MAKEUPD is provided as a form in VFP (DO FORM MAKEUPD) and a project and associated<br />

files that you can use to create an APP in FoxPro 2.x (DO MAKEUPD). MAKEUPD is simple:<br />

it asks you to specify which application you're interested in (including the version) and then<br />

creates an incremental distribution file for that application. It does this by opening<br />

DEVLOG.DBF, finding all entries for the specified application and version, and then reading<br />

each file specified in the MODULE field in each log record into the memo field of a table called<br />

UPDATE.DBF using the APPEND MEMO FROM command. This table thus contains the<br />

contents of each file changed in this version of the application.<br />

In addition to DEVLOG.DBF, MAKEUPD relies on a table called DIRECTRY (which is created<br />

the first time you run DEVLOG) that contains the directories where source code can be found for<br />

each version of an application. MAKEUPD uses this table so it can find where each of the<br />

modules logged in DEVLOG.DBF is located. If the source code for an application is spread out<br />

over more than one directory, create one record in DIRECTRY for each directory in the<br />

application. MAKEUPD will automatically recurse to each subdirectory under the defined<br />

directories, so you only have to define the top level directories used for each application. While<br />

you're filling out the contents of DIRECTRY, enter into the KEYFILE field the name and


location of a "key" file that must exist on the customer's site to ensure they're a valid owner of<br />

the product.<br />

INSTUPD.APP is used at the customer site to update their copy of the application with the<br />

changed files. The UPDATE table created by MAKEUPD is included in the INSTUPD project.<br />

The main program in INSTUPD.APP is very simple: it ensures the "key" file for the application<br />

can be found (MAKEUPD puts the name of this file, stored in DIRECTRY, into<br />

UPDATE.DBF), then it copies out each file from the memo field in UPDATE.DBF, overwriting<br />

any existing file on disk or installing a new file. Thus, INSTUPD updates the application with<br />

the most recent set of files. To create INSTUPD.APP, simply rebuild the INSTUPD project after<br />

running MAKEUPD.<br />

Improvements<br />

Although DEVLOG.APP has done yeoman service for us, several improvements could be made:<br />

• There's no built in reporting function. If you need to print reports by module, date, or<br />

application, those reports can easily be created.<br />

• Since VCXs can include many classes and each class can have many methods, additional<br />

fields could be added to DEVLOG.DBF so you can indicate which class inside the VCX<br />

was changed and which method was affected by the change if you want this level of<br />

detail in discrete fields.<br />

• The "make update" facility could be integrated with DEVLOG.APP.<br />

Conclusion<br />

If you're using version control software to manage application development, you probably<br />

already have the logging functionality described in this article. However, many of the smaller<br />

development shops I talk to don't use such software. If you fall into this category, you'll find<br />

DEVLOG as useful as we have.<br />

Next month we'll look at some "best practice" ideas for data handling in real world situations.<br />

We'll explore some strategies for handling multiple sets of data (such as test and production<br />

versions) in FoxPro 2.x and VFP, handling data validation errors, and other issues that come up<br />

in most applications.<br />

Doug Hennig is a partner with Stonefield Systems Group Inc. in Regina, Saskatchewan, Canada. He is<br />

the author of Stonefield's add-on tools for FoxPro developers, including Stonefield Data Dictionary for<br />

FoxPro 2.x and Stonefield Database Toolkit for Visual FoxPro. He is also the author of The Visual<br />

FoxPro Data Dictionary in Pinnacle Publishing's "The Pros Talk Visual FoxPro" series. Doug has<br />

spoken at user groups and regional conferences all over North America. 75156,2326@compuserve.com.<br />

Use Stonefield AppMaker to Create Custom


Components<br />

Kelly Conway<br />

If you've developed FoxPro applications for awhile you've experienced déjà vu when beginning<br />

another application from scratch. You create a directory structure here and copy a few programs,<br />

screens, and menus there. You may ask yourself, "Haven't I done this before?" You promise<br />

yourself that one of these days you'll write a program that will set it all up. Then, as the application<br />

progresses, you find yourself laying out and coding what seems like your one-millionth<br />

general-purpose data-entry screen. Wouldn't it be nice if you had a tool that would generate those<br />

standard screens for us?<br />

In 1994, Stonefield Systems Group Inc., makers of the popular Stonefield Data Dictionary<br />

(SDD) (reviewed in the February 1996 issue of <strong>FoxTalk</strong>), released a new product named<br />

Stonefield AppMaker. AppMaker addresses the problem of generating standard screens, and<br />

more.<br />

A complete, cross-platform application framework, AppMaker creates a standard directory<br />

structure and project files and also copies the necessary library files, main program, and menu<br />

files into the new directories. Then, after you've used SDD to define your tables (or import<br />

existing table definitions), AppMaker uses that information to quickly generate screens that, for<br />

most tables, might be used unmodified in the delivered application.<br />

But that's just the beginning. Read on to discover the wealth of features that AppMaker helps<br />

you to deliver in your applications.<br />

Using AppMaker to get started<br />

Rather than give you a laundry list of features, I'll relate those features by describing my recent<br />

experience using AppMaker to create a quick prototype of an application. I was charged with<br />

giving the client an idea of what we could do for them in FoxPro for Windows "without<br />

spending too much time. And, oh yeah, it would be nice if the prototype was complete enough<br />

that we could install it on the client's machine and let them play with it after we leave."<br />

How much time is "too much time?" Who knows? But, having used AppMaker to develop a few<br />

applications already, I knew that it was my best bet for creating a robust prototype in the shortest<br />

amount of time. I spent about an hour making a list of the functionality I wanted the prototype to<br />

have and then another hour sketching out and creating the tables and relationships.<br />

At 10 a.m. I created a directory for the prototype as well as a program called SETPATH. I used<br />

SETPATH to call AppMaker to ensure that its menu pad (Application) was added to the FoxPro<br />

menu bar and to issue the SET PATH command to include all of the subdirectories I needed to<br />

access.<br />

I then chose the New Application option from the Application menu pad. After filling out<br />

(4)


information such as the name of the project, the directory where it would be located, whether the<br />

application would require users to login upon startup, and which platforms would be targeted,<br />

AppMaker created a directory structure containing all of the files I needed for a new application.<br />

This included a set of project files that already contained references to the standard application<br />

objects (most of which were located in the shared directory structure, but some of which were in<br />

the project directories so that they could be modified for each project).<br />

At 10:10 a.m., I built the project into an .APP and started it. After logging in with the user name<br />

and password of SUPER, I saw a logo screen that contained the name of the application I had<br />

just created. I also noticed a fully functional menu system with File, Edit, Record, Screens,<br />

Report, Utilities, Windows, and Tools pads.<br />

The File pad contained Print Setup and Exit options. The Edit and Record pads were disabled<br />

(more about them later). The Screens pad contained no bars. The Reports pad contained fully<br />

functional options for Ad-Hoc Reports and Mail Merge. The Utilities pad's functions, Change<br />

Password, Customize, Reindex, and Security were also functional, as were the Cycle, Close All,<br />

About, and Help options of the Windows menu pad. The tools pad, available because I had<br />

inserted SET DEVELOP=T into the AUTOEXEC.BAT file, contained several functional options<br />

including Edit a Program, Edit Help, Suspend, Trace, Debug, Command window, View, and<br />

Array Browser.<br />

Not bad for 10 minutes worth of work (15 if you include the testing). At 10:15 a.m., I chose Data<br />

Dictionary from the Application and was presented with the familiar Stonefield Data Dictionary<br />

screen. I batch-added the tables I had created and entered a long description for each table, field,<br />

and index. I set up some of the tables to have a primary key that would be generated<br />

automatically by calling the Stonefield AUTOKEY() routine. I marked each of the tables (11 in<br />

all) to be auto-opened, reportable, and available in mail merges.<br />

I also defined each of the fields to be reportable and sortable and each index tag to be<br />

user-selectable as the current table order. In addition, I defined which fields were allowed to be<br />

left blank. I didn't do it for this prototype, but I could have defined any of the following screenoriented<br />

items for each field as well: picture, default value, when code, valid code, message,<br />

error, range, and help text. These tasks lasted until 11:30 a.m.. Man, am I hungry.<br />

At 12:30 p.m., I began to put the finishing touches on the table definitions and set up the<br />

relationships between the tables. The data dictionary even allowed me to specify referential<br />

integrity options for each relationship. For instance, I set it up so that the user wouldn't be<br />

allowed to delete a customer if they had one or more projects defined. But, if a project ID<br />

number got changed, it was allowed, but the change was cascaded into the project products table<br />

that contains the project ID as a foreign key. The options are, in fact, very similar to those that<br />

are available in Visual FoxPro.<br />

Having a fairly complete definition of the application's data, I turned my attention to defining the<br />

user interface.<br />

Creating quick screens, reports, and menu options


At 1:00 p.m., I used the Quick Screen option of Application to generate data-entry screens for<br />

the application's seven "look-up" tables. At 1:15 p.m., I built the application and tested those<br />

data-entry screens as well as the Ad-Hoc Reporting, Mail Merge, Reindex, and Update Table<br />

Structures options. Each worked flawlessly in a modeless, event-driven environment.<br />

However, AppMaker added the options to call my screens to the File menu pad. Since, I wanted<br />

these options to show up under my Screens menu pad, I had to do a little work in the menu<br />

builder to transfer those options.<br />

This might be a good time to mention the BASEDEF project that Stonefield uses to create new<br />

applications. BASEDEF is a regular FoxPro project that isn't meant to be built. Rather, you can<br />

modify its components so that the new applications AppMaker generates take on the<br />

characteristics you want. The default main menu in BASEDEF doesn't contain a Screens pad and<br />

it places the reporting options on a pad named Print.<br />

I added the Screens pad and renamed Print to Reports in BASEDEF's SYSMAIN menu. From<br />

that point on, all of my new application menus contain Screens and Reports pads. I could've also<br />

modified the Quick Screen process to add screens to my Screens menu pad rather than the File<br />

pad.<br />

Now, back to the prototype. After moving my menu options around and testing what had been<br />

built to this point, it was approaching 3 p.m. Time to move on to some reports. I ran the<br />

application and created quick reports for the seven lookup tables from the Ad-Hoc Reports<br />

option. Quick reports automatically used my field descriptions as report headings.<br />

These reports (and the look-up table screens too, for that matter) probably will be delivered<br />

unchanged in the final version of the application. Using the Ad-Hoc Reporting facility, the user<br />

will be able to specify order and filter conditions each time they run any of the reports. They<br />

even can save filters to be reused at a later date. About the only thing I've found that can't be<br />

done in the Ad-Hoc Reports is filtering on a non-field expression (for example, NOT<br />

DELETED()).<br />

Okay, it's 3:30 p.m. and I have a modeless, event-driven application that allows maintenance and<br />

reporting of seven small tables as well as a login screen, user maintenance, security, password<br />

change, customization (by user) options, ad-hoc reporting, mail merge, and table utilities<br />

(reindex, pack, and update table structures). The maintenance tables share a toolbar that sports<br />

nice-looking picture buttons for Add, Edit, Delete, Top, Prior, Next, Bottom, Find, List, Save<br />

Cancel, and Close functionality.<br />

The List option invokes a BROWSE window with incremental search capability on any column<br />

as well as the ability to sort on any column by moving the cursor into it. Find is a<br />

query-by-example implementation that allows the user to enter data into any combination of the<br />

screen's fields and then looks for matching records. If more than one match is located, the user is<br />

presented those record in the List function so they can choose the appropriate one. If you have<br />

the PhDBase text-search library installed, AppMaker automatically will use PhDBase indexes to<br />

support quick searches for the entered data contained anywhere in the corresponding field.


The Record menu pad contains an entry for each of these toolbar buttons as well as additional<br />

options for duplicating and filtering records. You may choose to use a different toolbar, such as<br />

one with text instead of pictures. Or you can create your own toolbar that calls the AppMaker<br />

functions. You can also tell AppMaker to generate push buttons in the screen and do away with<br />

toolbars altogether.<br />

The Edit menu pad contains the usual fare for cutting, copying, and pasting text.<br />

Oh yeah. The documentation says there's also a built in error handler, but I haven't seen it yet.<br />

There's still plenty of time in the day, so I press on.<br />

Creating more screens<br />

The prototype I'm building contains four main tables (in addition to the seven look-up tables I<br />

already handled). One is a customer table that contains about 30 fields, which is too many fields<br />

to put onto one screen (for my taste anyway). Sounds like a good time to try Steven Black's<br />

TABS driver for GENSCRNX. It uses GENSCRNX to create screens (actually screen sets) that<br />

mimic the tabbed screens that have become so popular in the last couple of years.<br />

Never used GENSCRNX? Not to worry -- I hadn't either, before AppMaker came along. It turns<br />

out that I've been using it all day now and didn't even know it. Stonefield wrote some<br />

GENSCRNX drivers that take care of writing the code to make AppMaker screens work in their<br />

event-driven framework and to be data-driven (use information stored in the data-dictionary such<br />

as the picture, default, message, and help clauses I mentioned earlier). Stonefield did the work of<br />

setting up GENSCRNX for me.<br />

But to use the TABS driver, I had to do a little work (very little) myself. I read the page about<br />

creating tabbed screens that appears in the AppMaker manual. Then I looked at (and copied) the<br />

example tabbed screen from the sample application that came with AppMaker to create my first<br />

tabbed screen. Between 3:30 and 5 p.m. I created three tabbed screens (the fourth main table<br />

ended up being another simple screen that gets called from one of the other three screens -- more<br />

on that later). One has two tabs, one has three tabs, and the other has four.<br />

Like any other relational database application, each of the "main-table" screens contains fields<br />

that link the records to the other main tables and to the lookup tables. Between 5 and 5:30 p.m., I<br />

set each of those fields up so that they'll validate the user's entry and bring up a list of valid<br />

choices if the entry isn't found. This functionality is provided by AppMaker's LOOK_UP<br />

procedure. Optionally, I could allow the user the choice of adding a new record to the related<br />

table and pass a parameter to LOOK_UP indicating which of the data-entry screens to bring up<br />

in add mode for this operation.<br />

In fact, I can use that add-mode functionality in other places. Remember that fourth table I<br />

mentioned earlier? It serves as a child table to one of the other three "main" tables. The related<br />

records from the child table are displayed in a list box on one of the tab pages of the parent table.<br />

Below that list box are buttons for adding, editing, and deleting child records. The Add button<br />

calls the child screen in add mode and returns immediately after the user chooses either Save or<br />

Cancel. The Edit button calls the child screen with the current record in edit mode and also


eturns after Save or Cancel is selected. The Delete button asks for confirmation and then<br />

removes the selected child record from the table.<br />

Why do I go into this in so much detail? We've all written this type of parent-child screen over<br />

and over, haven't we? Not me any more. All I did was draw a box where I wanted the child list<br />

box to appear and added a couple of GENSCRNX directives to the boxes comment snippet. I<br />

also created the Add, Edit, and Delete buttons and added a GENSCRNX directive to each of<br />

their comment snippets. AppMaker took care of the rest when I generated the screen.<br />

As an aside, AppMaker also allows you to create screens that contain integrated BROWSE<br />

windows by using the INBROWSE driver for GENSCRNX. The documentation is very thorough<br />

(seven pages) and the sample contains an example or two. I've used this feature and it does work.<br />

But I must warn you that, like any other method of integrating screens and BROWSE windows,<br />

the results aren't perfect. I got most of the functionality that I wanted in just a few minutes. But<br />

the last few things just aren't going to happen in any integrated BROWSE solution, primarily<br />

because of the BROWSE window's lack of Activate and Deactivate "events."<br />

Well, it's been a good day. It's nearly 7 p.m. and the prototype is ready to go. The client is going<br />

to love it! Of course, we'll have some more work to do after they accept our proposal. But<br />

AppMaker has created a good portion of the finished application for us. And it's all<br />

well-documented FoxPro programs and screens. AppMaker also contains several other<br />

data-driven routines not even mentioned here that will help us create most of the other options<br />

we'll need to add to this application.<br />

Modifying and extending AppMaker<br />

No matter how good a development framework is, there are always times when you need to do<br />

something a little differently or perhaps add something new that the framework doesn't address.<br />

This fact probably dissuades more developers from using third-party frameworks than any other.<br />

How can you make changes and additions to the product and still be able take advantage of the<br />

vendor's upgrades?<br />

In most respects, AppMaker isn't much different from most products on this issue. If you make<br />

changes to the framework, you'll have to do some work to merge those changes into the next<br />

update that comes from Stonefield. In addition to the obvious need to document these changes,<br />

here are a few strategies that help make this a less painful experience:<br />

1. When possible, externalize your changes. This means you should make your changes in a<br />

separate program that can then be called from the appropriate place in the framework program<br />

that needs modification. Then, when you receive an update, you need only to insert the call to<br />

your external programs into the new version.<br />

2. Create a subdirectory of your project to contain modified versions of the framework<br />

components. I call mine STONEMOD. I make a copy of the program or screen that needs to be<br />

modified, make the modification there, and point the project to that version rather than the<br />

centrally located one. With this strategy, you may never need to merge your changes into an<br />

updated version. You really only need to do so when the version adds a feature that your client


wants to have.<br />

3. If you're adding new functionality, place it in a separate subdirectory within the AppMaker<br />

directory structure. I call mine CUSTOM. This strategy will allow you to keep track of your<br />

enhancements without fear of overwriting them if Stonefield adds a feature with the same name<br />

in a future update.<br />

4. When you make a change, send a message to Stonefield to let them know what you have done.<br />

If the change has generic appeal, you may find that Stonefield will add it to the product (and give<br />

you credit in the comments), reducing the amount of work required to merge your changes into<br />

future updates.<br />

Using these strategies, I recently upgraded a client's application from an older version of<br />

AppMaker to the latest one in a couple of hours. When I was done, the application contained<br />

several enhancements, any one of which would have taken longer than that to implement.<br />

A different strategy<br />

So you just can't buy into someone else's application framework lock, stock, and barrel? Well,<br />

there are still ways that you can use Stonefield AppMaker. You could use the product to get<br />

ideas for designing your own framework (although you'd probably find it much easier to start<br />

with AppMaker and tailor it until it fits -- gaining the benefits of using the framework from day<br />

one).<br />

You also can use some of the data-driven routines to enhance existing applications. For instance,<br />

I'm saddled with maintaining a huge application that originally was written in dBASE IV. (Who<br />

isn't?) In less than a day I added all of that application's tables to Stonefield Data Dictionary,<br />

provided English descriptions for each table, field, and index and replaced the old, hard-coded<br />

Reindex routine with Stonefield's data-driven Reindex screen. Then, with a push button and four<br />

lines of code per screen, I added AppMaker's generic incremental search functionality to several<br />

existing data-entry screens. For grins, here's the necessary code:<br />

IF BROWSER()<br />

SCATTER MEMVAR MEMO<br />

SHOW GETS<br />

ENDIF<br />

Because BROWSER automatically takes care of opening the data-dictionary files (and closes<br />

them again unless they were already open), the simple syntax is all that is needed to add a great<br />

AppMaker feature into a legacy system. The users loved it.<br />

Part of the reason that it's easy to use AppMaker routines in a non-AppMaker application is the<br />

way in which those routines are documented internally. Each routine contains a section of<br />

comments titled "Environment In" that takes the guesswork out of which variables need to be<br />

defined, which tables need to be open, which index order they should be in, and so forth.<br />

Similarly, the "Environment Out" section of each routine's comments provides information about


what to expect after your program has called the routine.<br />

Documentation<br />

If you read the review of Stonefield Data Dictionary in the February 1996 <strong>FoxTalk</strong>, you're in for<br />

déjà vu. The documentation for Stonefield AppMaker is every bit as good as SDD's. The<br />

100-plus page (including an index) manual provides everything you need to learn how to use<br />

AppMaker. In addition, the documentation contains an appendix that explains how AppMaker<br />

uses GENSCRNX to generate screens that are aware of the information stored in your data<br />

dictionary.<br />

But the documentation doesn't stop with the manual. The product comes with full source code<br />

that's superbly commented. The requirements for using every routine are specified in a consistent<br />

program header that includes descriptions of any parameters and whether each is required or<br />

optional. In fact, the program documentation is so good that you'll probably find little need for<br />

the printed manual after having used the product long enough.<br />

When you install AppMaker, it creates a SAMPLE subdirectory that contains a complete sample<br />

application that was generated with AppMaker. You may find that running and examining the<br />

sample application is one of the best ways to quickly learn about AppMaker features.<br />

Technical support<br />

Technical support for Stonefield products also is superb. It's available via CompuServe, fax, and<br />

telephone. Following the trend set by many other vendors, Stonefield recently instituted a charge<br />

policy for telephone support. Priority support is available for $199 per year. This plan, which is<br />

free for the first 60 days, includes version updates and allows you to contact Stonefield by<br />

telephone, fax, or CompuServe. Standard support is free and is provided via CompuServe only.<br />

Standard support customers must pay for version updates (typically $99).<br />

When a bug is reported, it's fixed immediately and the fix is sent to the person who reported it. If<br />

it's a serious bug (one that could cause data loss, for example), an update is sent at no charge to<br />

all registered users. If the bug is minor or occurs only under unusual circumstances, it and other<br />

bug fixes are sent out periodically as a "maintenance release," which also is at no charge.<br />

In my experience, support has been excellent both over the phone and via CompuServe. Phone<br />

calls are answered or returned quickly, and CompuServe messages usually receive responses<br />

within 24 hours (often much more quickly than that). When I've reported bugs, the fix has<br />

always been delivered to me via CompuServe within 48 hours. Occasionally, enhancement<br />

requests have been handled in the same way.<br />

Pricing for AppMaker and other Stonefield products<br />

The cross-platform (DOS and Windows) version of Stonefield AppMaker is $249 (U.S). The<br />

product includes Stonefield Data Dictionary and Stonefield Reports. Developers who already<br />

own SDD can purchase just the combined AppMaker/Reports product for $149. SDD can be<br />

purchased separately for $99. Each comes with royalty-free source code and a 60-day<br />

unconditional money back guarantee. One copy is required per developer; multi-developer


licensing is available.<br />

Stonefield Reports (DOS and Windows, $99; no Mac version yet) is an end-user report manager.<br />

It allows users to select and print reports through an easy-to-use user interface. Its "quick report"<br />

function allows a user to quickly create a report by simply selecting which fields to output.<br />

Stonefield Database Toolkit for Visual FoxPro ($249 or upgrade from SDD for $149) extends<br />

the VFP database container to provide the features of Stonefield Data Dictionary. These features<br />

include the ability to define extended properties for tables, fields, and index tags as well as a<br />

class library of methods that you may use to add data management functions to your<br />

applications.<br />

For more information, contact Stonefield Systems Group by accessing their Web site (), sending<br />

CompuServe e-mail to 75156,2326, calling them at 800-563-1119 (306-586-3341 outside the<br />

United States and Canada), or by sending a fax to 306-586-5080.<br />

The bottom line<br />

If you already have a strong library of reusable routines and a method for quickly creating the<br />

screens and other components of your applications, then you may not need Stonefield<br />

AppMaker. But, if you're like most developers I know, you probably can't go wrong by investing<br />

the income from a few of your billable hours to receive the source code that has resulted from<br />

years of experience and several hundreds of development, testing, and documenting hours.<br />

Kelly Conway is an in-house software developer with Dimoco Manufacturing Company in Lee's Summit,<br />

Missouri. In the last eight years, he has developed FoxPro application for numerous clients in the Kansas<br />

City area. Kelly also is a co-founder and past president of The Midwest Fox Pros -- a FoxPro user group<br />

that serves more than 100 members from throughout the heartland.<br />

Create DBF-Style Help with HELPX, a<br />

GENSCRNX Driver<br />

Jeff B. Baker<br />

Creating an online Help system is usually the last part of the development process, like avoiding the<br />

dentist until the last possible moment. HELPX, a GENSCRNX driver, allows you to quickly and<br />

easily add DBF style Help to any screen in current or existing projects.<br />

You've just spent the last six months building the ultimate application for a client. The result far<br />

exceeds their expectations. The client asks when it can be installed. You hesitate for a moment<br />

(5)


then you say that it'll be another two months because you still need to write the Help system!<br />

Writing a Help system can be about as much fun as having wisdom teeth pulled.<br />

Most of my applications consist of data entry screens hanging off menus or called from other<br />

screens. Consequently, a large portion of the Help file will consist of topics associated with<br />

screen objects. Doesn't it make sense to add your Help directly into the screen through the<br />

Screen Builder as you're creating and adding the objects similar to commenting source code as<br />

you're writing it?<br />

HELPX, a GENSCRNX Help driver<br />

I took advantage of Ken Levy's GENSCRNX to create a simple but time saving solution for<br />

creating Help tables. In my opinion, the best feature of GENSCRNX is being able to write your<br />

own driver that "hooks" into GENSCRNX during screen generation. A GENSCRNX driver is<br />

perfect for generating DBF-style Help directly from your screens.<br />

HELPX allows you to add Help to the Comment field for any object on a screen. The generated<br />

DBF-style Help table is identical to FoxPro's own Help table. The topic is surrounded by a single<br />

line box, a summary for the topic can be added below the topic, the main text of the Help can be<br />

followed by an example, and one or more See Also topics can be included. All of this is<br />

accomplished by adding the appropriate HELPX screen directives in the Comment fields of<br />

screen objects.<br />

Help table structure<br />

The basic structure of a DBF-style Help table consists of three fields: Topic, Details, and Class.<br />

Topic contains a short topic description. Details is a memo field in which all information about<br />

the topic is placed, including the summary, examples, and see also topics. The Class field can<br />

contain any characters up to a total of 20. It can be used to filter out certain Help topics.<br />

As long as the three basic fields are included in a Help table, other fields may be added to<br />

incorporate additional features for a custom Help system. HELPX uses the three basic fields plus<br />

one additional field to Help during the Help table creation.<br />

HELPX directives<br />

To enable the HELPX driver, add the following command to each platform's CONFIG file:<br />

_SCXDRV5=helpx.prg<br />

This will automatically create Help for the current application's screens when the project is<br />

rebuilt.<br />

HELPX incorporates nine simple directives for the actual Help text. Add *:ADDHELP to the<br />

setup code snippet for every screen in which you wish to add Help. Add one or more of the<br />

following directives to the comment field for each object you want included in the Help table, .


The only required comment directive is *:HELPTOPIC. A Help record won't be created if the<br />

topic directive is missing.<br />

You'll need to take a few considerations into account. The text associated with the summary,<br />

main Help text, and example directives must start on the line following the directive. Any text on<br />

the same line as the directive will be ignored. New paragraphs in each of these sections can be<br />

created by pressing the enter key to move to the next line. MEMOWIDTH is set to 256, but you<br />

should limit the width of any one line to the Help window size depending up which platform<br />

you're using; otherwise, you may run into strange looking results. Table 1 contains the directives<br />

you can use.<br />

Table 1. Help text directives and their purposes.<br />

*:HELPTOPIC Defines the topic for the current object.<br />

*:HELPDESCSTART Defines the beginning of a summary section,<br />

whichmust start on the next line.<br />

*:HELPDESCEND Defines the ending of a summary section.<br />

*:HELPTXTSTART Defines the beginning of the main Help text,<br />

whichmust start on the next line.<br />

*:HELPTXTEND Defines the ending of the main Help text.<br />

*:HELPEXSTART Defines the beginning of the example text, which<br />

muststart on the next line.<br />

*:HELPEXEND Defines the ending of the example text.<br />

*:HELPSEEALSO A comma delimited list of See Also topics.<br />

*:HELPCLASS The class or classes for the Help topic, which arespace<br />

delimited.<br />

The following is an example of Help being added to a comment field. The topic, summary, Help<br />

text, see also, and class are shown:<br />

*:HELPTOPIC Client Name<br />

*:HELPDESCSTART<br />

The client name field contains the first and<br />

last name of the client.<br />

*:HELPDESCEND<br />

*:HELPTXTSTART<br />

This would be the main section of Help for the<br />

client name. If you wish to start a new line<br />

without word wrapping, press ENTER at the end<br />

of the line instead of letting the line wrap.<br />

Here could be the second paragraph of the main Help text:<br />

*:HELPTXTEND<br />

*:HELPSEEALSO CLIENT ID, ADDRESS, PHONE<br />

*:HELPCLASS General Client


If you already have an existing DBF-style Help table based upon the FoxPro Help structure for<br />

the rest of your application, append HELPX.DBF to incorporate your screen Help<br />

HELPER, a HELPX helper application<br />

A helper utility has been included to make adding Help directives and text to comment fields<br />

easier. It isn't required for HELPX, but it can make your life easier when developing a screen<br />

and adding Help.<br />

The Helper utility uses another great GENSCRNX driver, TABS, written by Steven Black.<br />

Helper consists of one screen with five tabs. The five tabs are Topic, where the topic and class<br />

are entered, Summary, Help, Example, and See Also (see Figure 1 and Figure 2). Open one of<br />

your screens and select the desired comment field of an object.<br />

Helper is designed to use the right mouse button to call it with an ON KEY LABEL in DOS,<br />

UNIX, or Windows. Ctrl-Z is used on the Mac since the Mac doesn't have a right mouse button.<br />

This key combination can be changed to whatever you desire.<br />

Once Helper has been brought up, enter only the information that will actually appear in the<br />

Help file, not the directives. Once all desired information has been entered, click on the Copy to<br />

Clipboard button and then Cancel. Then in the comment field, paste from the Clipboard by<br />

Ctrl-V. That's it! Directives and associated Help text will be placed in the comment field.<br />

Conclusion<br />

HELPX can't entirely automate your Help system, only the Help for your screens. All other Help<br />

must still be entered by hand. Since data entry applications tend to be screen oriented, you'll still<br />

save a considerable amount of time by using HELPX to create your Help files. If you have a<br />

custom Help table with additional fields, HELPX can be modified easily to incorporate new<br />

directives. HELPX takes what once might have been a chore, creating Help, and turned it into<br />

another indispensable tool.<br />

Jeff Baker is a FoxPro software engineer for Lifo Systems Inc., the nation's largest LIFO consulting and<br />

computation company serving more than 4,000 businesses and CPAs nationwide. When not<br />

programming, he can launches model rockets and flies stunt kites. jbb@airmail.net.<br />

Access the Clipboard Functions; Using a<br />

Form Handler<br />

John V. Petersen


I have an application in version 2.6a. In order to conserve screen space, I set SYSMENU<br />

OFF. Now I can't access the Clipboard functions of Cut, Copy, and Paste. Is there a way I<br />

can have access to these important functions without having the system menu present?<br />

Randy Strouth<br />

When the system menu is turned off, you lose access to any shortcut keys defined both by the<br />

menu and any pop-up definitions that currently reside in memory. This includes the standard<br />

Windows Clipboard functions you're attempting to use in your application. The key, then, is to<br />

not have access to the menu and at the same time, reclaim the screen real estate. SET<br />

SYSMENU TO won't work. Although you can't access the menu, the screen real estate isn't<br />

reclaimed. Enter this FoxPro command that some of us have forgotten: HIDE MENU.<br />

The system menu that contains the Windows Clipboard functions is called _MEDIT. At the top<br />

of the main program of your application, place the following code:<br />

HIDE MENU _msysmenu<br />

SET SYSMENU TO _medit<br />

While the second line of code isn't necessary, it takes care of releasing the additional system<br />

menus that aren't required.<br />

If you want to have the system menu suppressed when FoxPro starts, add this line to your<br />

configuration file:<br />

SYSMENU = OFF<br />

Now you get the best of everything. The reclaimed real estate, no menu, and access to the<br />

Windows Clipboard shortcuts in your application.<br />

What's the purpose of a form handler? They seem to be associated with most,<br />

if not all, available application frameworks. Isn't the Forms Collection<br />

associated with _SCREEN a native form handler? If it is, aren't we just<br />

re-inventing the wheel?<br />

Name withheld by request<br />

The basic purpose of a form handler is just that, it's an object whose sole purpose is to manage<br />

your form instances. While the _SCREEN object possesses a Forms Collection, it's by no means<br />

a form handler. Rather, the Forms Collection is an array of object references of currently<br />

instantiated forms and toolbars. Here enters the first limitation of relying on the forms collection<br />

to refer to forms. Toolbar objects are mixed in with form objects. A property of SCREEN,<br />

FormCount, counts both toolbar and form objects. For example, if you have a form and a toolbar<br />

instantiated, what's the value of _SCREEN.FormCount? The answer is 2! That probably isn't


what you expected. More importantly, your code might not expect this and in turn may break.<br />

Let's move on . . .<br />

What if you wanted to act on the group of forms as a whole? For example, what if you wanted to<br />

release your forms simultaneously and at the same time ensure that it's okay to release the form.<br />

After all, you may have uncommitted buffers in some of your forms. Also, what if you wanted to<br />

limit the number of instances of a forms that can be active at one time? In my February 1996<br />

column I suggested a way to utilize the Forms Collection to limit the number of instances. While<br />

this method will work, a custom form handler class is the way to go. Now, let's answer why this<br />

is so.<br />

When we create our own classes, we have the ability to exert the maximum amount of control<br />

and enjoy a great deal of flexibility in a situation. I say control because we have the ability to<br />

interject our own rules. I say flexibility because we can act on the group of forms as a whole.<br />

Lets look at a simple form handler class:


DEFINE CLASS cForms AS custom<br />

DIMENSION iaInstances[1,3]<br />

inForms = 0<br />

ioForm = .NULL.<br />

PROCEDURE Error<br />

LPARAMETERS nError, cMethod, nLine<br />

LOCAL lcMessage<br />

LOCAL ARRAY laError[1]<br />

=AERROR(laError)<br />

lcMessage = "The following error has occurred: " ;<br />

+ CHR(13) + CHR(10) + ;<br />

laError[2] + CHR(13) + CHR(10) + ;<br />

"Method: " + cMethod + CHR(13) + CHR(10) + ;<br />

"Line Number: "+ ALLTRIM(STR(nLine))<br />

=MESSAGEBOX(lcMessage,16,"An error has occurred")<br />

RETURN<br />

ENDPROC<br />

PROCEDURE Release<br />

RELEASE THIS<br />

ENDPROC<br />

PROCEDURE HowManyInstances<br />

LPARAMETERS tcClass<br />

LOCAL lnRetVal,lx<br />

lnRetVal = 0<br />

tcClass = UPPER(tcClass)<br />

FOR lx = 1 TO ALEN(THIS.iaInstances,1)<br />

IF !ISNULL(THIS.iaInstances[lx,1])<br />

llRetVal = THIS.iaInstances[lx,1].QueryUnload()<br />

IF UPPER(THIS.iaInstances[lx,1].Class) = ;<br />

tcClass<br />

lnRetVal = lnRetVal + 1<br />

ENDIF<br />

ENDIF<br />

ENDFOR<br />

RETURN lnRetVal<br />

ENDPROC<br />

PROCEDURE DoForm<br />

LPARAMETERS tcForm<br />

LOCAL lcID<br />

lcID = SYS(2015)<br />

THIS.inForms = THIS.inForms + 1<br />

DIMENSION THIS.iaInstances[THIS.inForms,3]<br />

IF FILE(tcForm+".SCX")<br />

DO FORM (tcForm) NAME THIS.iaInstances[THIS.inForms,1] LINKED<br />

ELSE<br />

THIS.IaInstances[THIS.inForms,1] = ;<br />

CREATEOBJECT(tcForm)<br />

THIS.IaInstances[THIS.inForms,1].Show()<br />

ENDIF<br />

WITH THIS<br />

.iaInstances[ THIS.inForms,1].Comment = lcID<br />

.iaInstances[THIS.inForms,2] = lcID<br />

i I t [ THIS i F 3]


Now I have some element of control. I can ask my form handler both questions about the forms<br />

that it manages and to delegate tasks to the forms as well. The following code takes care of<br />

running a form stored as an SCX:<br />

oForms.DoForm("myForm")<br />

The following code takes care of running a form stored as a class:<br />

oForms.DoForm("myFormClass")<br />

As you can see, I've provided a common interface via the DoForm method. The details of how<br />

the form is stored, (.SCX vs .VCX) are hidden from me. If I chose to cascade the forms, the<br />

following code will do the trick:<br />

oForms.Cascade()<br />

If I chose to poll all of my forms to see if it's okay to close down the application, I can issue the<br />

following code:<br />

IF oForms.QueryUnload()<br />

**code to shut down app<br />

ELSE<br />

**continue with app open<br />

ENDIF<br />

If I want to know how many of each class of form is instantiated, I just ask this question:<br />

? oForms.HowManyInstances("myformclass")<br />

At this point, it should be fairly obvious how beneficial and labor saving a<br />

forms handler can be.<br />

John V. Petersen, MBA, is director of FoxPro and Visual FoxPro development and training for Pearl<br />

Computer Systems Inc., a Microsoft Solution Provider and authorized SBT Reseller based in Mount<br />

Laurel, New Jersey (609-983-9265). John is active in the FoxPro community, has written for publications<br />

in the United States, and has been a speaker at user group meetings and at the 1995 Developer Days<br />

Conference. 103360.1031@compuserve.com.


Sidebar: Correction<br />

In my column in the March 1996 issue, I answered a question about refreshing the contents of a<br />

ListBox Control that is based on a view (the RowSourceType Property is set to Alias). In that<br />

response, my suggestion was to set the RowSource = RowSource and then to subsequently call<br />

the Refresh() Method of the ListBox Control. While this solution works, it's less than optimal.<br />

Once you've called the REQUERY() function on a view, all you need to do to refresh the<br />

contents of a ListBox or ComboBox is call the Requery() method of the control. -- J.P.<br />

Tip: Refreshing Page Frames from Hot Key<br />

Activations<br />

Mark Nadig<br />

I've run into a situation when using William O'Connor's idea for refreshing a page (Fast and<br />

Furious Pageframes, <strong>FoxTalk</strong>, May 1996). His refresh code assumes that the user will click on<br />

the desired page, but it's possible to set up a hot key for the page tab. In this case, the refresh<br />

code won't execute.<br />

Add the following code to handle this situation:<br />

PageFrame Init()<br />

LOCAL nPage<br />

FOR m.nPage = 1 TO This.PageCount<br />

This.Pages[ m.nPage].AddObject( "txtPageFix", ;<br />

"Wind2PageFix")<br />

ENDFOR<br />

RETURN<br />

Create the following class and add it to every page in a pageframe:


DEFINE CLASS Wind2PageFix AS TextBox<br />

Enabled = .F.<br />

TabStop = .F.<br />

Visible = .F.<br />

Width = 0<br />

Height = 0<br />

PROCEDURE UIEnable<br />

LPARAMETERS lEnable<br />

* This object is created for every<br />

* page in a pageframe.<br />

* The purpose is to handle pages not refreshing.<br />

IF m.lEnable<br />

LOCAL lOldLock<br />

m.lOldLock = Thisform.LockScreen<br />

Thisform.LockScreen = .T.<br />

This.Parent.Refresh()<br />

Thisform.LockScreen = m.lOldLock<br />

ENDIF<br />

RETURN<br />

ENDPROC<br />

ENDDEFINE<br />

The UIEnable method of the text box will fire on both page activation and deactivation. You<br />

need to refresh only on activate.<br />

Mark A. Nadig is product development technical manager at Wind2 Software Inc., a Fort Collins,<br />

Colorado publisher of accounting and business management software. 970-482-7145. CompuServe<br />

74250,1500.<br />

Modify Objects with ObjectExplorer<br />

Markus Egger (6)<br />

The ObjectExplorer is a tool that allows you to explore and modify objects in Visual FoxPro<br />

memory. Written by Markus Egger, the author of GenRepoX, ObjectExplorer is freeware and<br />

requires some basic know-how about the VFP object model. However, it's useful during<br />

development and is a great tool for delving deeper into VFP.<br />

The ObjectExplorer presents an outline view of all objects that have been instantiated in Visual<br />

FoxPro memory. The top level object is the "_screen" object -- the VFP screen that always exists<br />

and may or may not contain additional objects. Each property, event, and method of an object is<br />

represented by a ball icon; each object contained within the higher level object is represented by<br />

a closed book icon. Double-clicking on the closed book icon will change the icon to an open<br />

book and display a tree of that object's properties, events, and methods in outliner form.<br />

In Figure 1, the ObjectExplorer form shows the _screen object, the SuperClass object that has


een instantiated (see <strong>FoxTalk</strong>, January 1996 "Cool Tool: Ease Form Design Tasks with<br />

SUPERCLASS", for a description of SuperClass), and the ObjectExplorer object itself. As you<br />

can see, the properties, events, and methods for the _screen object are all listed, together with<br />

their value in the case of properties. You can switch between objects easily by using the<br />

drop-down combo box on the top of the form.<br />

This is an easy way to explore the structure of objects, since they can become very complex and<br />

details are easy to overlook. But besides that, this tool hasn't been very useful so far. Explore the<br />

_screen object again and look for the Closeable property. Now double-click this property and<br />

you'll see that the value of this property changed from .T. to .F. And if you watch the screen<br />

itself, you see that this property changed in real life too -- in Windows 95, for example, the Close<br />

box in the upper right corner of your screen has been dimmed. Now look for the caption<br />

property, double-click it, and you'll get the Insert a New Property dialog box as shown in Figure<br />

2.<br />

That's neat, but is it really useful? Yes it is. I'll show you some more functionality, and you'll see<br />

why. Look for the SaveAsClass method and double-click it to get the Fire an Event/Method<br />

dialog box as shown in Figure 3.<br />

You may have expected that double-clicking on a method or event would just execute it. Why<br />

didn't it? Sometimes you need to define parameters for your methods. In our example, the<br />

original value was _screen.SaveasClass(). We change this to<br />

_screen.SaveasClass("screen","screen"). Click OK, and the ObjectExplorer creates a classlib<br />

called screen and stores a class called screen in it. This class is a subclass of our Visual FoxPro<br />

screen. You can now edit this class with the visual class designer.<br />

OK, this is already very nice, but there's more. Open the Visual FoxPro class browser. Make the<br />

class browser the active form. Now click Refresh in the toolbar of the ObjectExplorer, but don't<br />

make the ObjectExplorer the active form (in other words, don't activate the ObjectExplorer<br />

window). It takes the ObjectExplorer a little while to be updated, since there are lots of objects in<br />

memory. After a few seconds, the ObjectExplorer comes up and shows the screen-object again.<br />

But remember that the class browser was the active form when the ObjectExplorer was updated.<br />

So expand the screen object and expand the ActiveForm object. Voilà! Here's our class browser,<br />

and we didn't even know how the class browser was called. You can now explore and modify the<br />

browser and, guess what, you can even save it as a class.<br />

We've already seen from the class browser example that there can be lots of items in the tree<br />

view, so let's look for ways to customize the ObjectExplorer. We can do this using the Explorer<br />

ToolBar as shown in Figure 4.<br />

As a default, this language is English but you can also change it to German, Portuguese, Spanish,<br />

French, and some others.<br />

The next five controls allow you to change the font and fontstyle in the Explorer window. You<br />

can also switch between a tree view and a +/- view. All these controls deal more with cosmetics<br />

than real functionality, but the next one is very important. It's the refresh button, which is used to<br />

update the information displayed in the Explorer window. You may be wondering why I didn't


integrate this button in the Explorer window itself. You couldn't use the ActiveForm property of<br />

the _Screen object, since the Explorer itself would always be the active form if the button would<br />

be on this form. But toolbars never become an active form, so the topmost window will show up<br />

as the active form in the Explorer window.<br />

The next two buttons are very useful if you want to customize the Explorer. The M button<br />

determines whether you want to see methods and events as well, or only properties. The default<br />

is for this button to be pressed meaning that all events and methods are displayed as well. The<br />

next button defines whether you want to see all properties, or only the ones with non-default<br />

values. This feature is only available in Visual FoxPro Version 3.0b or higher. By default this<br />

button isn't pressed, which means that all properties are displayed. You must refresh the Explorer<br />

window after pressing one of these buttons for the effects to take place.<br />

The next button, the Builder, works a lot like the builder button in the property sheet of the class<br />

and form designer. If you press this button, the ObjectExplorer searches an edited object in<br />

memory. This could be an object that is actually modified with the class or the form designer. If<br />

there is one, the whole structure of this object is displayed in the Explorer window. The only<br />

difference between a real life object (one that is actually running) and a builder object are the<br />

way events and methods are handled. When you explore a builder object, you can edit the code<br />

related to those events and methods instead of firing them.<br />

As you dig deeper into this complex tool called Visual FoxPro, you'll find that the tools you have<br />

to work with it haven't quite caught up. I hope you find the ObjectExplorer useful as one of a<br />

new line of developer tools for Visual FoxPro.<br />

Markus Egger has been involved in the computer business for more than 10 years. Markus owns the<br />

European software house EPS-Software and is involved in the American company MTI-Software. He<br />

works with Visual FoxPro and Visual C++, and is an international speaker and author.<br />

100337.1062@compuserve.com.<br />

Why Are You Here?<br />

Les Pinter<br />

[The following is based on introductory remarks by Les Pinter at the June 1996 Microsoft<br />

Developer's Conference in Obninsk, Russia. -- Ed.]<br />

Five years ago at the International Computer Conference in Moscow, a brash a young Russian<br />

programmer named Mikhail Korneev sat down beside me and abruptly said "So why are you<br />

here?" I didn't have a good answer, but I thought about it for a long time, and I'd like to answer<br />

it now. (Mike gets <strong>FoxTalk</strong>, so he'll be reading this as you read it. Privyet, Misha.)<br />

Forty years ago, as I sat in a classroom in Fairbanks, Texas, with 30 or so other Texas fourth<br />

graders, a car drove by our school and the light reflecting off the windshield flashed through our


window. We all dove under our desks because we thought it was the end of the world.<br />

This happened because 40 years earlier, just prior to the Russian Revolution, the situation in<br />

Russia was desperate. Lenin and his colleagues were saying to each other, "If we don't help each<br />

other, we won't have a future." Unfortunately, as we now know, things went from bad to worse<br />

and the result was the Cold War. That's why a classroom full of children all jumped under their<br />

desks when a bright light flashed through the window in Texas in 1956.<br />

Ten years ago Mikhail Gorbachev changed the world. As a result we now know that the<br />

government in Russia can't help us. So we have to help ourselves -- and each other.<br />

Eight years ago, I was desperate to find some clients for my new database consulting company in<br />

California. So I started a FoxPro newsletter and business got better. I came to understand that<br />

people really depended on my articles; they used them to get their work done and get paid. So by<br />

helping others, I helped myself. Eventually I wrote six books about FoxPro and things got a lot<br />

better.<br />

FoxPro is the single best opportunity for independent programmers in Russia to build a business.<br />

The need for database applications is huge and will grow even larger as people realize the power<br />

of information in business, and as the cost of clerical employees rises. It doesn't require a college<br />

degree; anyone who can read can build a business. In the process, programmers help themselves<br />

and they help Russia turn its back on the past. And regardless of what you hear (or don't hear)<br />

from Microsoft marketing people, FoxPro beats the pants off VB and Access. Introducing young<br />

Russian programmers to FoxPro and helping them get up to speed is the only thing I can think of<br />

to fend off the resurgence of the bad old days. It's important.<br />

Six years ago I received an article submission from a young Russian programmer with a Moscow<br />

phone number at the bottom of the page. I looked at that letter for several weeks, and finally<br />

decided to give the guy a call. The eventual result was that we began to publish a Russian edition<br />

of my newsletter. Today that same young man works for Microsoft, makes a good living, and<br />

travels all over the world. And the Pinter FoxPro Letter has more than a thousand Russian<br />

subscribers who depend on it to help them earn a living -- a decent, honest living. Over half of<br />

them are independent contractors, the finest of Russia's honest self-employed. They show<br />

Russians that you don't have to be a criminal to make a good living.<br />

Last year I began to organize groups of programmers in Moscow and Ukraine to do work for my<br />

American clients. We send specifications by Internet and our programmers return the finished<br />

programs. We now have four groups of FoxPro developers who live in Russia and Ukraine but<br />

work in America. They earn a good living, and eventually most of them will fly over and meet<br />

their clients on the way to Disneyland or Maui. Then they'll go home and tell their colleagues<br />

what the market economy is all about. Perhaps it seems like a small thing, but show me what the<br />

American government has done to help my friends on the other side of the world. And it's indeed<br />

a small world.<br />

Why am I here? I'm here because I made that telephone call. I'm here because there are in this<br />

very room several programmers who have a better job, and a better future, because we took a<br />

risk. I'm here because I think perhaps Lenin was right: If we don't help each other, we won't have


a future.<br />

Les Pinter publishes the Pinter FoxPro Letter in the United States and Russia. 916-582-0595.<br />

SQL and Readable Code<br />

Barbara Peisch<br />

Steps to Help Debug Your SQL<br />

If you've written a long SQL SELECT statement only to have FoxPro return an error, you know<br />

how frustrating it can be to find the location of the problem. Track down the error by breaking<br />

up your code and trying each part.<br />

Start by running just the code that select fields and specifies the tables the data is coming from.<br />

Make sure you include any join conditions necessary in your WHERE clause, but don't include<br />

any filter conditions. (An example of a join is WHERE Parent.Invoice = Child.Invoice. An<br />

example of a filter is WHERE InvDate BETWEEN Date1 and Date2.) If you have an error in<br />

that part, try eliminating the fields one by one until the problem disappears. If you suspect the<br />

problem might be with one of the join conditions in your WHERE clause, eliminate all the fields<br />

that rely on that join and remove the condition from the WHERE clause to confirm or disprove<br />

your suspicion.<br />

If you can run the select with all your fields and joins, add in any filter expressions you had in<br />

your WHERE clause.<br />

If you have a UNION, try running each select within the UNION separately to see which one has<br />

the problem.<br />

If you have several UNIONs and each of your selects runs independently, try running the first<br />

two. If that works, keep adding one more until the error reappears.<br />

Sometimes the problem isn't with the specific code you used, but where it occurs. If you can, try<br />

switching the order of parts of your select to see if the problem occurs on the same code or if it<br />

occurs in the same position.<br />

. . . And on Another Note<br />

If you use a lot of command button groups in your 2.x applications, you know how easy it is to<br />

forget what order the buttons are in when trying to write code to enable or disable specific<br />

buttons. You might have something like this in your SHOW clause:


DO CASE<br />

CASE <br />

SHOW GET , 1 DISABLE<br />

SHOW GET , 2 ENABLE<br />

SHOW GET , 3 DISABLE<br />

CASE <br />

SHOW GET , 1 ENABLE<br />

SHOW GET , 2 DISABLE<br />

SHOW GET , 3 DISABLE<br />

OTHERWISE<br />

SHOW GET

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!