Many years ago, when I encountered my first computer, the term "programming" had a very limited connotation. In fact, the word "computer" itself invariably evoked an image of a roomful of equipment set off in its own climate-controlled domain, surrounded by whirring tape drives and ablaze with flashing lights. Programming one of those iron workhorses meant setting out line after line of cryptic coded symbols in the mysterious act of communicating with the machine.
There was no GUI. The tools at that time were mainly of the hardware variety, such as keypunch machines to transform handwritten programs into machine-readable format and console dials to display and alter problematic memory locations. Debugging relied heavily on the primitive art of "desk-checking," a time-consuming but necessary evil in the days of 24-hour turnaround time. Programmers visually traced line by line through every branch of a program in order to exercise its logic and head off failures.
In the most general terms, programming means getting a machine to do what you want it to do. But the current computer scene is many magnitudes different from the early days of mainframes, having grown up in a variety of directions. Today's seemingly infinite combinations of hardware and software have produced correspondingly complex environments. As the selection of hard and soft components has broadened, so has the number of ways to accomplish that communication, and programming has come to mean many things to many people.
Some of the most casual computer users refer to using their employers' custom applications as "programming." Others use the term when they undertake a mail-merge operation through their word-processing program, or enter formulas into a spreadsheet, or design a building renovation using CAD. Macros and other scripting tools in some sophisticated programs enable the more adventuresome user to string together an intricate series of keystrokes and visual commands to perform a complicated task. In some cases, such as the Microsoft Office suite of applications, the system generates code from the user's macro entries. This Visual Basic for Applications (VBA) code can be displayed, saved as a program file, modified by the user, and rerun in its customized form. Examining code generated by tools is an excellent way to learn programming syntax.
In the world of Visual FoxPro, with its rich cache of development tools and apparently overwhelming choices of how to perform any particular task, there are also many different activities that can be classified as programming, a situation compounded by FoxPro's own evolution.
A Brief History of Visual FoxPro |
In 1984, Fox Software introduced FoxBase to compete with the desktop database product it was modeled after, Ashton-Tate's dBASE II. At that time FoxBase was little more than a programming language and a data-handling engine. FoxPro acquired a GUI in 1989, and in 1991 took off solidly in its own innovative territory with the release of FoxPro 2.0. That product contained a fully integrated version of SQL, unequalled performance capabilities, open architecture in its development tools, and several design surfaces that actually generated code and applications from a user's onscreen manipulations. FoxPro evolved into Visual FoxPro in 1995. It retained its procedural capabilities, primarily to maintain backward-compatibility, but added tremendously to its professional stature by becoming a fully realized object-oriented language as well. And, of course, there were still more development tools to work with! VFP is now in its third major release, version 6. Its outlook is very much expanded to encompass interaction with other major desktop and client/server products, and to build Web-based applications. |
At the heart of all such activities, both in and out of the VFP environment, there is a type of interaction that goes back to the earliest days of computing. In the remainder of this chapter, you will explore what it means to produce your own programs using VFP elements and stylistic conventions.
Over the years, FoxPro has grown into Visual FoxPro, and each evolutionary step has enabled programmers to do more by simply and directly manipulating the tools supplied with the product. When you used FoxBase, you were responsible for coding everything, including your application's user interface. With FoxPro 2.0, you were given Power Tools that generated interface programs for you. Now there are wizards, builders, and design surfaces that mostly bypass code generation, but create a sophisticated interface for your applications nonetheless. You don't need to write much code at all.
So, before you get to the specifics of "how," let's address the issue of "why" you should learn to write programs:
One of the best ways to learn good programming practices is to find examples and study them. Well-commented, clearly laid out code will show you the possibilities.
The FoxPro line of development products has historically maintained an open architecture. Many of the tools that come with it are written in VFP's own language. When you use the design surfaces to build forms, classes, projects, reports, and database containers, the details of the entities you create are stored in VFP tables. Many developers use these programs and components as guidelines for their own programming efforts.
VFP 6 comes with a host of new tools beyond what was supplied in previous versions. Look for some of them in the subdirectories below the one in which VFP is installed, also known as HOME(). You can switch to it by simply typing CD HOME() in the Command window, or use the Windows Explorer to browse through the offerings. You should see subdirectories named Tools and Wizards, which contain many source items used to build the various applications.
NOTE |
Visual FoxPro versions 3 and 5 came with wizards that generated application components to accomplish typical tasks. You could work with what the wizards created, but the wizards themselves were supplied in the form of compiled applications. In other words, you had no way of knowing how they worked their wizardry. In response to considerable outcry from the development community, Microsoft has supplied an even larger number of wizards with VFP 6 and a full set of source code for these and other tools. Look for a file named Xsource.zip in the subdirectory \Tools\Xsource\ under the VFP home directory. Unzip this file and use your software's option to maintain the directory structure in the zip file. This will enable you to view all of these programs, libraries, project files, and other components. |
As if this weren't enough, VFP 6 comes with a variety of samples that are enhanced to take advantage of this version's new features. The samples have been moved, along with the help files, into subdirectories of the MSDN Library that comes with Visual FoxPro. You can access information about the samples and utilities through the Help interface, as shown in Figure 2.1, or by typing ?HOME(2) in the Command window to locate the path where you can find the files directly.
NOTE |
The term "online help" takes on a whole new meaning with this release of VFP. Whether you purchase the standard, standalone development product or the entire Visual Studio suite, you receive a single-edition MSDN CD containing all the help files, samples, source, and additional support documentation for all the development tools. For a nominal yearly fee, you can become an MSDN Library subscriber and receive periodic updates. For free, you can access the MSDN Library contents by visiting Microsoft's new documentation Web site at msdn.microsoft.com (or go directly to the Visual FoxPro section at msdn.microsoft.com/vfoxpro). The Web site contains current technical and marketing product information for all the tools in Visual Studio, including samples, demos, and a considerable amount of downloadable resources. You will also find links to other sites relating to Visual FoxPro, third-party marketing sites, and independent support locations. |
Just as every spoken language contains nouns, verbs, adjectives, and syntax rules for putting them all together, so every computer programming language contains certain basic features. Visual FoxPro's language is no exception, and contains the following:
Visual FoxPro provides a rich supply of instructions to accomplish individual tasks. Each subsequent release of FoxPro since the earliest days of Xbase has aimed to maintain backward compatibility with previous versions. Consequently, the language has become "bloated" with commands that are infrequently used but require continued support only because their removal would break legacy code. And, of course, each new release adds exciting new features to the language.
Documentation for all native VFP commands and functions is available in the Language Reference portion of the online Help file. Figure 2.2 shows the broad range of activities handled by the language components. To get to this view, select Help, Microsoft Visual FoxPro Help Topics from the system menu (or press F1) to bring up the new help file supplied on a separate CD, MSDN Library Visual Studio 6.0. In the left pane, drill down through Visual FoxPro Documentation, Reference, Language Overview, Language Categories, and through the list of command groups. Click on any category to display a list of instructions in the right-hand pane related by their intended use. Click on an individual command's hyperlink to reveal more detailed information about its syntax and use.
NOTE |
The lists within each category are not exhaustive. The lists are a start, but check the alphabetical listing of the entire set if you don't find what you want in this breakdown. |
If you know what instruction you want to use, you can go directly to its detailed description in various ways:
TIP |
The VFP color-coded program editor can provide unexpected visual cues to more information. For example, suppose you think you need to write a function to accomplish a task you don't know is covered in the native language. If you type a name for it and notice it change from standard black to the color of a keyword, you've stumbled over a resource you might not have to re-create! Highlight it and press F1 for more information. |
However you arrive at it, you must follow the syntax exactly. VFP provides many options for issuing and using instructions, but it expects precision in terms of spelling, arrangement of parameters, and punctuation between clauses and lists.
NOTE |
Since its earliest days, FoxPro has followed the Xbase convention that enables you to reduce keywords to their initial four letters. This feature was easier to count on with the limited command set of older versions, but it continues to be a handy convenience, especially when typing in the Command window. You can type your programs this way too, but be forewarned that programs in this format can be hard to follow. Not only must you unravel the logic of a routine, but you might first have to translate each four-letter root into its expanded keyword. |
VFP is not case sensitive. Typically, developers adhere to their own standards, or those of their employers, to differentiate between VFP keywords and user-supplied options. While some developers type everything in lowercase so that they don't have to be bothered with the Shift key at all, many follow the practice of coding keywords in all uppercase, using mixed case for user-defined functions, filenames, variables, and so on. This is strictly a readability convention, as there are no restrictions within the language itself that dictate such rules. The same is true of blank lines and spaces. Within a program, one space is the same as several spaces between words, and many blank lines are no different from one blank line. The best practice is to use plenty of blank space and indentations to produce source code that is easy to read and therefore easy to maintain.
Commands, like direct orders, are words grouped together to instruct the computer to take an action. Visually, they differ from functions, which are also computer instructions, in that functions end in a pair of parentheses. The parentheses can be empty or they can contain any parameters recognized by that function. Traditionally, a function returns a result, and that result is available at the point where the function call is made, as shown in this example:
ldDate = DATE()
In this line of code, the variable ldDate contains today's date after the function executes.
There are various ways to issue a function call:
lnStart = SECONDS() && Number of seconds since midnight < Other program commands > lnDuration = SECONDS() - lnStart && Calculate elapsed time
IF TABLEUPDATE() < Some processing if function was successful > ENDIF
?SEEK(lcRecord, lcTable, lcIndex) && Display results of search
lnCurentMonth = MONTH( DATE() )
=ASORT(laArray) && Sort an array
ADIR(laFiles, '*.PRG') && Create array, one entry per program file
NOTE |
Sometimes you don't care what the return value is. You want a function to perform simply as a command, and you're willing to accept its default behavior. Unadorned function calls, which didn't exist prior to VFP 5.0, can apply to native functions, user-defined functions, and object methods. |
The simplest form of information you can use in your code is unvarying data that you supply directly to your routines. A value that is coded into the routine that uses it is taken literally and thus is called a literal. For example:
lnWeeks = lnDays / 7 && The number of days in a week is always 7 MessageBox('Processing Complete') && The message is always the same lnElapsed = DATE() - {06/01/1998} && The start date remains the same DIMENSION aArray[15,3] && The size of this array is fixed
If you assign a name to a literal, it becomes a constant. All references to that name in your code will be replaced by the value you supply. If you need to change the value, you only have to change it in one place, where the assignment is made, as shown in this example:
lnEmployeeCount = 53 <Some processing> FOR lnI = 1 TO lnEmployeeCount <Print invitations to the company picnic> ENDFOR
TIP |
It's not a good idea to embed literals throughout your programs, particularly if there's the slightest chance that these values can change. Name them descriptively as constants and place them prominently in your code, usually in the beginning, for easier maintenance. Better yet, find a central place to store constants that pertain to your application. You can read such constants out of a data table or locate them in object properties. |
Constants can be assigned at runtime, as shown in the previous example, or at compile time with #DEFINE directives, such as the following:
#DEFINE ZERO 0.
Groups of #DEFINE statements can be maintained in a separate #INCLUDE file that you add to the beginning of your programs. Memory usage is reduced somewhat because the substitution is made during compilation, and resources are immediately released. This can, arguably, increase performance because the named constants don't need to stay in memory and references have already been resolved by the time they are encountered.
It's not always reasonable to expect to know ahead of time all the values that you want your code to use. Just as in an algebraic expression, you can assign a name to a value and then use that name as a stand-in for the actual value anywhere in your program code.
The simplest way to assign a value to a name is with the equal sign (=). You can also use STORE SomeValue TO SomeName. This syntax is convenient when you want to assign a single value to many variables at once:
STORE 0 TO lnOne, lnTwo, lnThree, lnFour, lnFive
Visual FoxPro enables you to manage a rich assortment of data types in memory.
In VFP, a variable takes on the characteristics of the data it contains. If x = 5, x is a numeric variable. If the value of x is changed so that x = 'ABC', the variable's type is also changed to character. Furthermore, a variable that hasn't been established in any formal way is simply created the first time you refer to it by assigning it a value.
This chameleon-like quality of VFP variables is known as weak-typing, a distinguishing characteristic of the language that is not universally shared. Other languages, such as C++, require that you declare variables before you use them and that you identify and stick to their assigned data type.
NOTE |
Weak-typing can get you in trouble in your programs. Commands and functions expect to find data in a particular form. If you refer to a variable of the wrong type, as shown in Figure 2.3, your code will generate an error. |
NOTE |
Prior to VFP 6, the only sure way to determine a variable's current type was to test it with the TYPE() function. Unfortunately, the syntax for this function causes untold confusion. |
Scope refers to the life span and visibility of a variable. Variables can be
Public variables, also known as global variables, can be seen and used in any routine during the current VFP session. They remain in memory until they are released or until VFP terminates.
NOTE |
Traditionally one program passed information to others via global variables. These might include logical flags indicating that certain processes had finished, so it was okay for something else to start. Now that VFP provides an object-oriented environment, many developers prefer to pass object references instead of memory variables because an object can contain a whole range of related data and behavior in its packaging. See Part IV, "Object-Oriented Programming," for more information on how this is accomplished. |
Private variables exist in the current program. They are automatically released when the current program terminates if they haven't been specifically released before then. Private variables are also visible to any routines called by this one, which can lead to confusion in the called routine if it has its own variables with the same name. If no other declaration is made about a variable's type, VFP assumes that it is private.
Local variables have been around since VFP 3, and they only exist in the current routine. If a variable is local to an object method or other subroutine, it is released when the routine finishes.
TIP |
|
CAUTION |
Explicit declarations of scope do not create variables or guarantee data type. PUBLIC gcPubVar, PRIVATE pnNumVar, and LOCAL ldDateVar only establish the intended life span of those variables once they are created. Until values are assigned, the variable type remains undefined, regardless of scope. |
Arrays An array is a group of memory variables maintained as a unit. It can be visualized as a spreadsheet, where horizontal rows intersect with vertical columns at individual points called cells, and each cell can hold a piece of information. In fact, each cell can hold information of a different type from that of its neighboring cells.
Arrays are akin to data tables, with which they share terminology. Table records are sometimes called rows, table fields are referred to as columns, and the many fields in a given record can be of mixed types.
Of course, tables are permanent representations of data that can be used from session to session, whereas arrays are more like variables in that they remain in memory for the duration of their processing life, and then they are released. While an array is in scope, you can access any single cell of data by its row and column numbers, like this:
laPayment[1,5] = laPayment[1,3] * lnRate && Multiply hours x Rate for this entry
NOTE |
Notice in the preceding line of code that the array coordinates are set off between square brackets ([]). This is not a requirement, as parentheses could just as easily be used for the same purpose. Many developers use brackets, however, to distinguish array cell references from function calls. |
Arrays are very handy constructs for preparing intermediate results in the course of a routine. Because they remain entirely in memory, access is very fast. VFP has many native array-handling functions that are very efficient at filling, searching, sorting, and arranging data in arrays.
Naming Conventions Because of the confusion that can arise when a routine uses data of the wrong type or references a variable that has gone out of scope, many developers have adopted the recommended practice of embedding scope and type information into variable names. Typically a two-letter, lowercase prefix is concatenated to a descriptive name, such as lcStudentID, pdToday, or glOkToContinue. The prefix letters and their meanings are listed in Table 2.1.
Table 2.1 Typical Prefixes for Variable Naming Conventions
Scope (First Letter) | |
Letter |
Meaning |
l |
Local |
p |
Private |
g |
Public (global) |
t |
Parameter |
Type (Second Letter) | |
a |
Array |
c |
Character |
y |
Currency |
d |
Date |
t |
Datetime |
b |
Double |
f |
Float |
l |
Logical |
n |
Numeric |
o |
Object |
U |
Unknown |
Indirect Referencing and Macro Expansion So far, you've looked at instructions and memory resident data. You've learned that commands and functions pose direct instructions to the computer and pass arguments that are actual pieces of information or named variables that stand in for data.
Some commands, particularly those that work with data in tables, expect names as arguments: names of tables, names of fields, names of reports. Names aren't subject to assignment in the same way that data can be assigned to a variable name. Yes, you can populate a variable with a name, but then it is simply a character string that has lost its inherent meaning.
Indirect referencing enables you to issue commands using names that vary. For example, you might read the name of the current backup file out of a field in a system table. In your program, you then surround that name with parentheses to indicate that the character string actually represents a name of another entity, and it's that other entity you want to reference:
lcFileName = System.Backup USE (lcFileName) IN 0 ALIAS Backfile
You can take the issue of indirection a step further. Sometimes you are not even sure what instructions you want to run. In that case, you can extend the power of the VFP language by building strings of commands, clauses, and other reserved keywords, and then use them as if they were written into your program.
To accomplish this feat, you use a technique called macro expansion. Simply add an ampersand (&) to the front of a variable name, which signals to VFP that the literal contents of the variable should become part of the current execution string. The following is a simple example that shows how this feature works. Type it in the Command window and press F9:
x = "ON KEY LABEL F9 WAIT WINDOW 'I pressed F9' " &x
Macro expansion is often used to restore environment settings at the end of a routine in which they have been changed. For example:
lcTalk = SET( 'TALK' ) && Capture the setting of TALK SET TALK ON SELECT * FROM MyDBF ; WHERE < criteria > SET TALK &lcTalk && Restore TALK the way it was before
Data that is kept in a table is subject to more rigorous identification than data in memory variables. Each field in the table has a fixed size and data type. Further, if the table is part of a database, there can be rules set up in database stored procedures that monitor values, conditions of uniqueness, and relationships with other tables.
Because data fields are limited to some predefined presentation, it is not as important to impose naming conventions on them. A data field of numeric type will not accept characters, for example. A logical field can only accept values of .T. (True) or .F. (False), no matter what you might try to enter instead. Field characteristics are stored in the table header and are available to the application on request.
See Part II, "Working with Data," for more information about data in tables and databases.
A comment line is one that starts with an asterisk (*) or the word NOTE. You can also add comments to any line of code by skipping at least one space and typing two ampersands (&&). Everything from the ampersands to the end of the line is considered a comment and is ignored by VFP.
Even though they are not compiled or run, comments serve a very important purpose in programming. Code can be very complex. Once it is written and in use, a program might not be looked at for months. Frequently the only reason to go back through a working program is that some new set of circumstances has caused it to fail. Code that is difficult to read is even more difficult to fix and maintain. Liberal additions of comments that spell out a routine's intended behavior make the task much easier for whoever has to pick up the logical threads. You will appreciate the effort even if it is your own code you are reviewing.
It is also a very good idea to place a heading of comment lines at the beginning of your coded routines. You might include such things as program author, creation date, purpose, expected output, parameter identification, and calling syntax. Many development shops set a standard look for such headers, which then serve as part of the overall project documentation.
VFP supplies a full set of symbol operators that manipulate data according to its type. The operations can be strung together to form complex expressions, but all data within such an expression must be of the same type. Some of the symbols produce different behaviors for different data types, as shown in Tables 2.2 through 2.5.
NOTE |
Each table's set of symbols is presented in the priority order in which they are evaluated within an expression. It is the same concept that you learned in high school math: My Dear Aunt Sally stood for Multiplication, Division, Addition, Subtraction. |
Table 2.2 Character String Operators
Operator |
Action |
+ |
Concatenates two character values. |
- |
Concatenates, while removing trailing blanks from the first value. |
$ |
Searches for one character string within a second. |
Table 2.3 Arithmetic Operators
Operator |
Action |
( ) |
Groups values to increase execution priority |
**, ^ |
Exponentiation |
* |
Multiplication |
/ |
Division |
% |
Modulus (remainder from division) |
+ |
Addition |
- |
Subtraction |
Table 2.4 Logical Operators
Operator |
Action |
() |
Groups values to increase execution priority |
NOT, ! |
Logical negative; reverses an item's value |
AND |
Logical AND |
OR |
Logical inclusive OR |
Table 2.5 Date Math Operators
Operator |
Action |
+ |
Addition (produces a date in the future) |
- |
Subtraction (calculates duration) |
VFP uses a set of symbols, listed in Table 2.6, to evaluate the logical truth of an expression. These symbols, also known as relational operators, compare two values of the same type, resulting in a value of .T. if true or .F. if false.
Table 2.6 Logical Evaluators
Symbol |
Comparison |
< |
Less than |
> |
Greater than |
= |
Equal to |
<>, #, != |
Not equal to |
<= |
Less than or equal to |
>= |
Greater than or equal to |
== |
Exact string comparison |
You won't always want your code to run every line for every condition. The power of programming languages comes from the capability to decide whether a particular action is appropriate to each and every different type of data or some other environmental circumstance. In VFP, you use these constructs to determine whether to proceed with one or another set of options:
IF lnAge > 10 IF lnAge > 15 IF llAllRequirementsFilled < Schedule graduation > ELSE < Schedule counseling > ENDIF ELSE < Schedule a particular test > ENDIF ELSE < Schedule a practice session > ENDIF
lcGrade = IIF(lnGPA > 2.0, 'Passed', 'Failed') lcMaritalStatus = IIF(UPPER(lcCode) = 'M', 'Married', ; IIF(UPPER(lcCode) = 'D', 'Divorced', ; IIF(UPPER(lcCode) = 'S', 'Single', 'Unknown') ) )
DO CASE CASE lnGroupSize < 10 lnRate = lnFullRate CASE lnGroupSize < 25 lnRate = lnFullRate * ln1stDiscount OTHERWISE lnRate = lnFullRate * ln2ndDiscount ENDCASE
Looping constructs are special types of commands that divert program execution from its straight-line path. Loops enable you to specify some logical condition that determines whether to repeat a selected set of instructions or continue with the processing stream beyond the loop.
A loop consists of a pair of commands that bracket a coded routine. A condition is tested at the beginning of the loop, and if it evaluates to true, the subsequent instructions are run; otherwise, control continues after the END statement. VFP offers the following ways to execute loops:
DO WHILE lnNumEnrolled < lnMaxSize < Continue enrolling students until the class is full > ENDDO
SCAN FOR Employee.Status = 'Active' < Processing applies only to current employees > ENDSCAN
For lnI = 1 TO ALEN(aVendors, 1) < Write checks for each of the vendors in the array > ENDFOR
FOR EACH oColumn IN THIS.oGrid.Columns oColumn.Width = oColumn.Width * 1.25 oColumn.Text1.Width = oColumn.Width ENDFOR
All of these looping mechanisms enable you to use a LOOP or an EXIT command to escape processing before reaching the END statement. LOOP sends you back to the top to reevaluate the continuation condition. EXIT takes program control beyond the end of the loop to the remainder of the program. See Chapter 23, "Error Detection and Handling," for more information about loops.
You can enter and run lines of code in the VFP Command window. In earlier versions of FoxPro, you could only enter a single line of code at a time followed by a press of the Enter key. Certain coding constructs that require multiple lines, such as IF…ENDIF or DO…ENDDO, could not be run in the Command window because the Enter key also acted as a signal to run whatever was typed.
In VFP, this limitation has been lifted. You can enter as many lines of code as you want and use the mouse or keyboard arrow keys to reposition the cursor. When you're done, highlight all the lines you typed and press Enter, or right-click the mouse and choose Execute Selection. The entire block runs as if it were a single line of code.
Running code in the Command window is a good way to test that it performs as you expect it to and to work out any kinks. If you want to be able to run it again without typing it every time, you must save the code block in a file as a program, procedure, function, or method.
A VFP program is simply a text file with a .PRG extension. You create such a file when you type MODIFY COMMAND in the Command window. VFP opens an editing window with the default name Program1.prg. You have the option of changing that name when you save the file for the first time, or you can choose File, Save As from the system menu. If you type MODIFY COMMAND MyProg, VFP will first look for a program named MyProg.prg in its path. If it finds the file, VFP opens it for editing. If it doesn't find a match, it opens a new empty window with that name.
TIP |
Remember that because you can abbreviate most VFP instructions to four characters, you can type MODI COMM to accomplish the same thing as MODIFY COMMAND. |
Now that you have an open window, enter your program, one instruction per line. It improves program readability to keep lines of code to a length that is viewable on a computer screen or a printed page. If one line isn't enough to complete an instruction, VFP enables you to continue onto the next line by typing a semicolon (;) at the end of the current line. Program readability is more than a stylistic concern. You will find that your programs are easier for you and others to maintain if you format them simply and clearly and keep a minimal amount of information on each line.
After you enter all required lines of source code into your program, you can direct VFP to convert it into object code by selecting Program, Compile from the menu. This step is not required, however, because VFP will compile it when it needs to-for example, when you type DO MyProg in the Command window. Regardless of when the program is compiled, VFP creates an .FXP file of the same name (MyProg.fxp) in the same directory where it finds the .PRG file (MyProg.prg).
TIP |
VFP bases its decision to compile on DOS file dates. Unless you issue a specific directive to compile, VFP will not recompile if it finds an .FXP file that is newer than the .PRG file of the same name in the same directory. This can lead to confusion over which version of code you are running. It is always safe to delete any questionable .FXP files and let VFP recompile what it needs. |
For purposes of compiling or running code, VFP assumes that anything it finds in a program file is program code, with the exception of comments.
CAUTION |
If a comment line ends in a semicolon, VFP treats the following line as a continuation of the comment, even if the next line doesn't begin with an asterisk. VFP continues to concatenate lines as long as it encounters semicolons, excluding more code than you might intend, as shown in the following code sample: |
SET PATH TO * I've changed the first line of the SET PATH statement, * but I want to save it so I can change it back. * SET PATH TO \olddata, \oldlibs, ; SET PATH TO data, libs, ; progs, include, ; graphics, menus, ; metadata, reports DO MyProg && VFP will not be able to find progs\MyProg.prg
By definition, a program file is a standalone .PRG file. There are no other special directives to identify it as such. A program file, however, can contain subroutines known as procedures and functions that you can run from within or outside your program. Functions are also known as UDFs, or user-defined functions. Subroutines can be packaged with other commonly used routines into a procedure file that makes no pretense of being anything more than a container. Such a file can be identified by a .PRC or .PRG extension.
A procedure is a routine in which the first and last lines are defined as follows:
PROCEDURE ProcedureName <your code lines> ENDPROC
Likewise, a function is a coded routine bracketed by FUNCTION…ENDFUNC commands.
NOTE |
ENDPROC and ENDFUNC can be replaced with RETURN, but any of the three are strictly for readability. You actually don't need to enter any type of terminating command, because VFP will recognize the end of the routine when it encounters an end of file or the start of another PROCEDURE or FUNCTION. |
Typically, you execute a procedure the same way you would a program: via DO ProcedureName or DO ProcedureName IN ProgramName. A function call looks different. You simply type the function name followed by a set of parentheses:
FunctionName()
Program Flow At runtime, program instructions are executed line by line, one after another, except in the case of subroutines. When your program encounters an instruction to run a procedure or a function, it's as if all the lines of code in that routine were inserted immediately after your call. This is true whether the subroutine is in the same or a different file. At the completion of the subroutine code, execution continues at the line following the call, as shown in this example:
* This is the main program. ACTIVATE SCREEN ?'Executing main program code.' DO Proc1 ?'Back in the main program after Proc 1' x = Func1() ?'x = ' + x ?'Remaining program statements go here.' RETURN PROCEDURE Proc1 ?'Executing 1st line of subroutine: Proc1' ?'Executing 2nd line of subroutine: Proc1' ENDPROC FUNCTION Func1 LOCAL lcReturnVal ?'Executing subroutine code: Func1' lcReturnVal = 'Character string prepared in Func1' RETURN lcReturnVal
You can place as many procedures and/or functions as you want within a single file, but they must all appear after the last line of your main program. Don't type any lines of main program code following the subroutines, as they will never be reached at runtime.
What's the Point? You might be asking: If a subroutine is executed as if it were an inline part of the main program, why bother with moving it off to another location?
Subroutines add power and flexibility to a program. Name them descriptively, pass them parameters, and you can do any of the following:
What's the Difference? Up to now I've talked about procedures, functions, and subroutines as if they were all the same. So, what is the difference? The simple answer is that conceptually there is no difference! They are all just routines subordinate to a program with many variations in how you can interact with them.
By convention, a function returns a value (but it doesn't have to) and a procedure probably does not return anything (but it can). After the first line, the differences are so easily overridden as to be academic. The major difference is that procedures and functions make different default assumptions about the nature of parameters passed to them. But again, these default assumptions are easily changed to suit your needs.
Even the different calling styles for procedures and functions are not hard-and-fast rules. You can run a function as if it were a procedure:
DO FunctionName
And you can call a procedure as you would a function, via assignment to a variable:
lcVar = ProcedureName()
Passing Parameters Much of a subroutine's power comes from being able to perform actions on a variety of data. If you want the calling program to supply information for use in the subroutine, the first non-comment line of code after PROCEDURE or FUNCTION must be PARAMETERS or LPARAMETERS. LPARAMETERS, like the LOCAL declaration itself, specifies that the scope is local to the subroutine. Parameters can be any type of data or expressions, up to 27 in a single routine.
By default, parameters are passed to a procedure by reference and to a function by value. By reference means that the subroutine receives a pointer to the actual variable. Any changes the subroutine makes are applied directly to the original. By value means that the subroutine sees a copy of the original variable. Its value at the time it is passed can be used in the subroutine, but the original remains intact in the calling program.
It's easy to override default parameter-passing behavior:
Methods are simply a form of object-oriented packaging for subroutines. Methods use the same code construction principles as always, but the execution of that code is timed to coincide with an event that a particular object senses and responds to. You can find out more about the object model and adding method code to objects in Chapter 9 "Creating Forms," Chapter 15, "Creating Classes with Visual FoxPro," and Chapter 17, "Advanced Object-Oriented Programming."
The principal distinguishing characteristic of method code is the means by which it is fired. You run a method by entering its name as a command any place that you can enter program instructions. The name of the method must be fully qualified by the entire object hierarchy that defines its location. For example:
THISFORM.pgfPageFrame.Page1.cntPageObj.cntMover.cmdMoveButton.Click()
Although parentheses are not required, many developers usually follow a method name with a set of parentheses to identify it as a routine rather than object property.
Methods are not as universally available within an application as programs and subroutines tend to be. Access to a method is restricted by runtime conditions: Has the object been instantiated? Is there an available object reference? Is the method hidden or protected? The chapters in Part IV, "Object-Oriented Programming," explain how to gain access to method code.
As with other types of subroutines, you can pass parameters to a method, and you can expect it to pass back its results.
SQL is not new to VFP. One of FoxPro's major strengths, early on and all along, has been its subset of SQL code fully integrated into the language. You can find out more about the commands and their syntax from the VFP help system and from the chapters of this book that deal with the Query and View Designers: Chapter 6, "Creating Basic Queries," Chapter 7 "Advanced Queries and Views," and Chapter 8 "Accessing Remote Data." After you start using the actual builders, you will continue to learn the nuances of SQL because the design surfaces generate and display code in response to your entries.
Anything you can imagine doing to data in tables can be accomplished via procedural code. Procedural code works on data one record at a time, and requires that your programs handle all the necessary I/O.
SQL, on the other hand, delivers a set of data that meets conditions you specify in a single command. Admittedly, that command can potentially contain a very complex arrangement of clauses. Given the right set of conditions, however, VFP is free to optimize its access to the data. All I/O is handled behind the scenes. The process can be lightening fast.
This section is not intended to be an exhaustive list of new features in Visual FoxPro 6. Look for information on new Access and Assign methods in Chapter 9 "Creating Forms," and Chapters 15-17 on class creation and management. See also Chapter 19, "The Visual FoxPro Component Gallery," and Chapter 18, "The Visual FoxPro Foundation Classes." Chapter 23, "Error Detection and Handling," talks about the enhanced Coverage Profiler application. And Part V, "COM," deals with many of the new interoperability features.
In terms of the VFP programming language, there are minor enhancements throughout that support the other new features. This mainly amounts to new clauses for existing commands and new properties, events, and methods in support of such features as OLE Drag and Drop, Active Document Applications, Coverage.app, and the new ProjectHook class.
There are some new and enhanced functions as well:
Table 2.7 Foxtools.fll Functions Added to VFP 6
Function |
Description |
ADDBS() | Adds a backslash to a path. |
DEFAULTEXT() | Adds an extension if there is none. |
DRIVETYPE() | Identifies drive type. |
FORCEEXT() | Changes filename to include new extension. |
FORCEPATH() | Changes filename to use new path. |
JUSTDRIVE() | Returns the drive letter from a filename. |
JUSTEXT() | Returns only the extension from a filename. |
JUSTFNAME() | Returns only the filename, without extension. |
JUSTPATH() | Returns only the path from a filename. |
JUSTSTEM() | Reduces filename to first eight characters. |
© Copyright, Sams Publishing. All rights reserved.