Chapter 1: Introduction to Programming

In Visual FoxPro, procedural and object-oriented programming work together so you can create powerful, flexible applications. Conceptually, you can think of programming as writing a sequence of instructions for accomplishing specific tasks. At a structural level, programming in Visual FoxPro involves manipulating stored data.

If you are new to programming, this chapter helps you get started. If you are familiar with other programming languages and want to see how Visual FoxPro compares, see Visual FoxPro and Other Programming Languages. For an explanation of object-oriented programming, see Chapter 3, Object-Oriented Programming.

This chapter covers:

Advantages of Programming

Generally, anything that you can do in a program can also be done manually if you have enough time. For example, if you wanted to look at information about a single customer in a table of customers, say the Ernst Handel company, you could do it manually by following a specific sequence of instructions.

To manually find a single order in a table

  1. From the File menu, choose Open.

  2. From the Files of type box, choose Table.

  3. Double-click Customer.dbf in the list of files.

  4. From the View menu, choose Browse.

  5. Scroll through the table, checking the Company field of the records for “Ernst Handel.”

Programmatically, you could achieve the same results by typing the following Visual FoxPro commands in the Command window;

USE Customer
LOCATE FOR Company = "Ernst Handel"
BROWSE

After you locate the order for this company, you might want to increase the maximum order amount by 3 percent.

To manually increase the maximum order amount

  1. Tab to the max_ord_amt field.

  2. Multiply the value in max_ord_amt by 1.03 and type the new value in the field.

To achieve the same result programmatically, type the following Visual FoxPro command in the Command window:

REPLACE max_ord_amt WITH max_ord_amt * 1.03

It is relatively simple to change the maximum order amount for a single customer, either manually or by typing the instructions in the Command window. Suppose, however, that you wanted to increase the maximum order amount of every customer by 3 percent. To do this manually would be laborious and prone to mistakes. If you give the right instructions in a program file, Visual FoxPro can accomplish this task quickly and easily, without error.

Sample Program to Increase Maximum Order Amounts For All Customers

Code Comments
USE customer
Open the CUSTOMER table.
SCAN
Go through every record in the table and perform all the instructions between SCAN and ENDSCAN for every record.
REPLACE max_ord_amt WITH ;
  max_ord_amt * 1.03
Increase the maximum order amount by 3%. (The semicolon (;) indicates that the command is continued on the next line.)
ENDSCAN
End of the code that is executed for every record in the table.

Running a program has some advantages over entering individual commands in the Command window:

The following sections detail the mechanics, concepts, and processes behind this and other Visual FoxPro programs.

The Mechanics of Programming
in Visual FoxPro

You can program Visual FoxPro by writing code: instructions in the form of commands, functions, or operations that Visual FoxPro can understand. You can include these instructions in:

Using the Command Window

You can execute a Visual FoxPro command by typing it into the Command window and pressing ENTER. To re-execute the command, move the cursor to the line containing the command and press ENTER again.

You can even run multiple lines of code from the Command window as though they were a self-contained program.

To run multiple lines of code in the Command window

  1. Select the lines of code.

  2. Press ENTER or choose Execute Selection from the shortcut menu.

Since the Command window is an editing window, you can edit commands using the editing tools available in Visual FoxPro. You can edit, insert, delete, cut, copy, or paste text in the Command window.

The advantage of entering code in the Command window is that the instructions are carried out immediately. There is no need to save a file and run it as a program.

In addition, choices you make from menus and dialog boxes are echoed in the Command window as commands. You can copy and paste these commands into a Visual FoxPro program, then run the program repeatedly, making it easy to execute thousands of commands again and again.

Creating Programs

A Visual FoxPro program is a text file containing a series of commands. You can create a program in Visual FoxPro in one of these ways:

To create a program

  1. In the Project Manager, select Programs under the Code tab.

  2. Choose New.

    -or-

  3. From the File menu, choose New.

  4. In the New dialog box, select Program.

  5. Choose New File.

    -or-

Visual FoxPro opens a new window named Program1. You can now type your program into this window.

Saving Programs

After you create a program, be sure to save it.

To save a program

If you try to close an unsaved program, a dialog box opens, prompting you to either save or discard the changes you made to it.

If you save a program that was created from the Project Manager, the program is added to the project.

If you save a program that hasn’t been named yet, the Save As dialog box opens so you can specify a name for the program. After you save your program, you can run or modify it.

Modifying Programs

After you save your program, you can modify it. First, open the program in one of these ways:

To open a program

After you open the program, you can make changes. When you finish making your changes, be sure to save the program.

Running Programs

After you create a program, you can run it.

To run a program

Writing Code in the Visual FoxPro Design Tools

The Form Designer, Class Designer, and Menu Designer allow you to easily integrate program code with the user interface so that the appropriate code executes in response to user actions. The Report Designer allows you to create complex and customizable reports by integrating code into the report file.

To take advantage of the full power of Visual FoxPro, you need to use these design tools. For more information about the Report Designer, see Chapter 7, Designing Reports and Labels, in the User’s Guide. For more information about the Class Designer, see Chapter 3, Object-Oriented Programming, in this book. For more information about the Form Designer, see Chapter 9, Creating Forms, and for more information about the Menu Designer, see Chapter 11, Designing Menus and Toolbars.

Basic Programming Concepts

When you program, you store data and manipulate it with a series of instructions. The data and data storage containers are the raw materials of programming. The tools you use to manipulate this raw material are commands, functions, and operators.

Storing Data

The data you work with probably includes amounts of time, money and countable items, as well as dates, names, descriptions, and so on. Each piece of data is a certain type: it belongs to a category of data that you manipulate in similar ways. You could work directly with this data without storing it, but you would lose most of the flexibility and power of Visual FoxPro. Visual FoxPro provides numerous storage containers to extend your ability to easily manipulate data.

Data types determine how data is stored and how it can be used. You can multiply two numbers together, but you can’t multiply characters. You can print characters in uppercase, but you can’t print numbers in uppercase. Some of the primary data types in Visual FoxPro are listed in the following table:

Data Types

Type Examples
Numeric 123
3.1415
– 7
Character “Test String”
“123”
“01/01/98”
Logical .T.
.F.
Date

DateTime

{^1998-01-01}

{^1998-01-01 12:30:00 p}


Data Containers

Data containers allow you to perform the same operations on multiple pieces of data. For example, you add the hours an employee has worked, multiply them by the hourly wage, and then deduct the taxes to determine the amount of pay the employee has earned. You’ll have to perform these operations for every employee and every pay period. If you store this information in containers, and perform the operations on the containers, you can just replace the old data with new data and run the same program again. This table lists some of the main containers for data in Visual FoxPro:

Type Description
Variables Single elements of data stored in your computer’s RAM (Random Access Memory).
Table Records Multiple rows of predetermined fields, each of which can contain a predefined piece of data. Tables are saved to disk.
Arrays Multiple elements of data stored in RAM.

Manipulating Data

Containers and data types give you the building blocks you need to manipulate data. The final pieces are operators, functions, and commands.

Using Operators

Operators tie data together. Here are the most common operators in Visual FoxPro.

Operator Valid Data Types Example Result
= All
? n = 7
Prints .T. if the value stored in the variable n is 7, .F. otherwise
+ Numeric, Character, Date, DateTime
? "Fox" + "Pro"
Prints “FoxPro”
! or NOT Logical
? !.T.
Prints .F.
*, / Numeric
? 5 * 5
? 25 / 5
Prints 25
Prints 5

Note   A question mark (?) in front of an expression causes a new line character and the results of the expression to be printed in the active output window, which is usually the main Visual FoxPro window.

Remember that you must use the same type of data with any one operator. The following statements store two numeric pieces of data to two variables. The variables have been given names that start with n so we can tell at a glance that they contain numeric data, but you could name them with any combination of alphanumeric characters and underscores.

nFirst = 123
nSecond = 45

The following statements store two pieces of character data to two variables. The variables have been given names that start with c to indicate that they contain character data.

cFirst = "123"
cSecond = "45"

The following two operations, addition and concatenation, yield different results because the type of data in the variables is different.

? nFirst + nSecond
? cFirst + cSecond

Output

168
12345

Because cFirst is character data and nSecond is numeric data, you get a data type mismatch error if you try the following command:

? cFirst + nSecond

You can avoid this problem by using conversion functions. For example, STR( ) returns the character equivalent of a numeric value and VAL( ) returns the numeric equivalent of a character string of numbers. These functions and LTRIM( ), which removes leading spaces, enable you to perform the following operations:

? cFirst + LTRIM(STR(nSecond))
? VAL(cFirst) + nSecond

Output

12345
168

Using Functions

Functions return a specific type of data. For example, the functions STR( ) and VAL( ), used in the previous section, return character and numeric values, respectively. As with all functions, these return types are documented along with the functions.

There are five ways to call a Visual FoxPro function:

Some other examples of functions used in this chapter are:

Function Description
ISDIGIT( ) Returns true (.T.) if the leftmost character in a string is a number; otherwise, returns false (.F.).
FIELD( ) Returns the name of a field.
LEN( ) Returns the number of characters in a character expression.
RECCOUNT( ) Returns the number of records in the currently active table.
SUBSTR( ) Returns the specified number of characters from a character string, starting at a specified location in the string.

Using Commands

A command causes a certain action to be performed. Each command has a specific syntax which indicates what must be included in order for the command to work. There are also optional clauses associated with commands that allow you to specify in more detail what you want.

For example, the USE command allows you to open and close tables:

USE Syntax Description
USE
Closes the table in the current work area.
USE customer
Opens the CUSTOMER table in the current work area, closing any table that was already open in the work area.
USE customer IN 0
Opens the CUSTOMER table in the next available work area.
USE customer IN 0 ;
  ALIAS mycust
Opens the CUSTOMER table in the next available work area and assigns the work area an alias of mycust.

Some examples of commands used in this chapter are:

Command Description
DELETE Marks specified records in a table for deletion.
REPLACE Replaces the value stored in record field with a new value.
Go Positions the record pointer to a specific location in the table.

Controlling Program Flow

Visual FoxPro includes a special category of commands that “wrap around” other commands and functions, determining when and how often the other commands and functions are executed. These commands allow conditional branching and looping, two very powerful programming tools. The following program illustrates conditional branches and loops. These concepts are described in more detail after the example.

Suppose that you had 10,000 employees and wanted to give everybody making $30,000 or more a 3 percent raise, and everybody making under $30,000 a 6 percent raise. The following sample program accomplishes this task.

This program assumes that a table with a numeric field named salary is open in the current work area. For information about work areas, see “Using Multiple Tables” in Chapter 7, Working with Tables.

Sample Program to Increase Employee Salaries

Code Comments
SCAN
The code between SCAN and ENDSCAN is executed as many times as there are records in the table. Each time the code is executed, the record pointer moves to the next record in the table.
   IF salary >= 30000.00
      REPLACE salary WITH ;
         salary * 1.03
For each record, if the salary is greater than or equal to 30,000, replace this value with a new salary that is 3% higher.

The semicolon (;) after WITH indicates that the command is continued on the next line.

   ELSE
      REPLACE salary WITH ;
         salary * 1.06
For each record, if the salary is not greater than or equal to 30,000, replace this value with a new salary that is 6% higher.
   ENDIF
ENDSCAN
End of the conditional IF statement.

End of the code that is executed for each record in the table.


This example uses both conditional branching and looping commands to control the flow of the program.

Conditional Branching

Conditional branching allows you to test conditions and then, depending on the results of that test, perform different operations. There are two commands in Visual FoxPro that allow conditional branching:

The code between the initial statement and the ENDIF or ENDCASE statement is executed only if a logical condition evaluates to true (.T.). In the example program, the IF command is used to distinguish between two states: either the salary is $30,000 or more, or it isn’t. Different actions are taken depending on the state.

In the following example, if the value stored in the variable nWaterTemp is less than 100, no action is taken:

* set a logical variable to true if a condition is met.
IF nWaterTemp >= 100
   lBoiling = .T.
ENDIF

Note   An asterisk at the beginning of a line in a program indicates that the line is a comment. Comments help the programmer remember what each segment of code is designed to do, but are ignored by Visual FoxPro.

If there are several possible conditions to check for, a DO CASE ... ENDCASE block can be more efficient and easier to keep track of than multiple IF statements.

Looping

Looping allows you to execute one or more lines of code as many times as you need to. There are three commands in Visual FoxPro that allow looping:

Use SCAN when you are performing a series of actions for each record in a table, as in the example program just described. The SCAN loop enables you to write the code once and have it executed for each record as the record pointer moves through the table.

Use FOR when you know how many times the section of code needs to be executed. For example, you know there are a specific number of fields in a table. Because the Visual FoxPro function FCOUNT( ) returns this number, you can use a FOR loop to print the names of all the fields in the table:

FOR nCnt = 1 TO FCOUNT( )
   ? FIELD(nCnt)
ENDFOR

Use DO WHILE when you want to execute a section of code as long as a certain condition is met. You might not know how many times the code will have to execute, but you know when it should stop executing. For example, let’s assume you have a table with people’s names and initials, and you want to use the initials to look people up. You would have a problem the first time you tried to add a person who had the same initials as someone else already in your table.

To solve the problem, you could add a number to the initials. For example, Michael Suyama’s identification code could be MS. The next person with the same initials, Margaret Sun, would be MS1. If you then added Michelle Smith to the table, her identification code would be MS2. A DO WHILE loop enables you to find the right number to append to the initials.

Sample Program with DO WHILE to Generate a Unique ID

Code Comments
nHere = RECNO()
Save the location of the record.
cInitials = LEFT(firstname,1) + ;
   LEFT(lastname,1)
nSuffix = 0
Get the person’s initials from the first letters of the firstname and lastname fields.

Establish a variable to hold the number to be added to the end of a person’s initials if necessary.

LOCATE FOR person_id = cInitials
See if there is another person in the table with the same initials.
DO WHILE FOUND( )
If another record in the table has a person_id value that is the same as cInitials, the FOUND( ) function returns true (.T.) and the code in the DO WHILE loop executes.
If no match is found, the next line of code to be executed is the line following ENDDO.
   nSuffix = nSuffix + 1
   cInitials = ;
      LEFT(cInitials,2);
      + ALLTRIM(STR(nSuffix))
Prepare a fresh suffix and append it to the end of the initials.
   CONTINUE
CONTINUE causes the last LOCATE command to be evaluated again. The program checks to see if the new value in cInitials already exists in the person_id field of another record. If so, FOUND( ) will still return .T. and the code in the DO WHILE loop will execute again. If the new value in cInitials is indeed unique, FOUND( ) will return .F. and program execution continues with the line of code following ENDDO.
ENDDO
End of the DO WHILE loop.
GOTO nHere
REPLACE person_id WITH cInitials
Return to the record and store the unique identification code in the person_id field.

Because you can’t know beforehand how many times you’ll find matching identification codes already in use, you use the DO WHILE loop.

The Process of Programming

When you understand the basic concepts, programming is an iterative process. You go through the steps many times, refining your code as you go. When you are starting out, you test frequently, using a lot of trial and error. The more familiar you become with the language, the more quickly you can program and the more preliminary testing you can do in your head.

The basic programming steps include:

Here are some things to remember when you are getting started:

The rest of this section traces the steps in constructing a small Visual FoxPro program.

Stating the Problem

Before you can solve a problem, you need to formulate it clearly. Sometimes if you adjust the way you formulate the problem, you’ll be able to see more or better options for solving it.

Suppose you get a lot of data from various sources. Though most of the data is strictly numeric, some data values contain dashes and spaces in addition to numbers. You should remove all the spaces and dashes from those fields and save the numeric data.

Instead of trying to remove spaces and dashes from the original data, you could formulate the goal of the program as:

Goal   Replace the existing values in a field with other values that contain everything from the original values except the spaces and dashes.

This formulation avoids the difficulty of manipulating a string of characters whose length keeps changing as you work with it.

Breaking the Problem Down

Because you have to provide specific instructions to Visual FoxPro in terms of operations, commands, and functions, you need to break the problem down into discrete steps. The most discrete task for the example problem is to look at each character in the string. Until you can look at a character individually, you can’t determine whether you want to save it.

After you look at a character, you need to check to see if it is a dash or a space. At this point, you might want to refine the statement of the problem. What if you get data later that contains open and closed parentheses? What if you want to get rid of currency symbols, commas, and periods? The more generic you can make the code, the more work you can save yourself later; the whole point is to save work. Here is a formulation of the problem that works with a much greater variety of data:

Refined Goal   Replace the existing values in a field with other values that contain only the numeric characters from the original values.

With this formulation, you can now restate the problem at the character level: if the character is numeric, save the character; if the character is not numeric, move on to the next character. When you have constructed a string that contains only the numeric elements of the initial string, you can replace the first string and move on to the next record until you have gone through all the data.

To summarize, the problem breaks down into these pieces:

  1. Look at each character.

  2. Decide if the character is numeric or not.

  3. If it is numeric, copy it to the second string.

  4. When you have gone through every character in the original string, replace the original string with the numeric-only string.

  5. Repeat these steps for all the records in the table.

Constructing the Pieces

When you know what you need to do, you can start to formulate the pieces in terms of Visual FoxPro commands, functions, and operators.

Since the commands and functions will be used to manipulate data, you need some test data to work with. You want the test data to resemble the actual data as closely as possible.

For this example, you can store a test string to a variable by entering the following command in the Command window:

cTest = "123-456-7 89 0"

Look at Each Character

First, you want to look at a single character in the string. For a list of functions that can be used to manipulate strings, see Character Functions.

You will find three functions that return specific sections of a string: LEFT( ), RIGHT( ), and SUBSTR( ). Of the three, SUBSTR( ) returns characters from any part of the string.

SUBSTR( ) takes three arguments or parameters: the string, the initial location in the string, and the number of characters to return from the string, starting from the initial location. To test if SUBSTR( ) is going to do what you want, type the following commands in the Command window:

? SUBSTR(cTest, 1, 1)
? SUBSTR(cTest, 3, 1)
? SUBSTR(cTest, 8, 1)

Output

1
3
-

You can see that the first, third, and eighth characters of the test string have been displayed in the main Visual FoxPro window.

To do the same thing a number of times, use a loop. Since the test string has a specific number of characters (14), you can use a FOR loop. The counter in the FOR loop is incremented each time the code in the loop is executed, so you can use the counter in the SUBSTR( ) function. You could test the looping constructs in the Command window, but at some point you’ll want to save your work to build on it later. Now would be a good time to create a new program.

To create a new program

  1. Type the following command in the Command window:
    MODIFY COMMAND numonly
    
  2. In the window that opens, type the following lines of code:
    FOR nCnt = 1 TO 14
    ? SUBSTR(cTest, nCnt, 1)
    ENDFOR
    

Now that you’ve created a program, you can run it.

To run a program

  1. In the open program window, press CTRL+E.

  2. If a Save dialog box appears, choose OK.

When you run this program, the individual characters in the test string are printed on separate lines in the main Visual FoxPro window.

Testing Part of the Program

The first task has been accomplished. You can now look at each character in the string.

Decide if the Character is Numeric

After you have fetched a single character from the string, you need to know if it is a number. You can find this out using ISDIGIT( ).

Type the following commands in the Command window:

? ISDIGIT('2')
? ISDIGIT('-')
? ISDIGIT(SUBSTR(cTest, 3, 1))

Output

.T.
.F.
.T.

From this output, you can see that ‘2’ is a number, ‘– ’ is not a number, and the third character in cTest, 3, is a number.

If the Character is Numeric, Copy it to the Second String

Now that you can look at the characters and determine whether they are numeric, you need a variable to hold the numeric values: cNumOnly.

To create the variable, assign it an initial value, a zero-length string:

cNumOnly = ""

As the FOR loop moves through the string, it’s a good idea to create another variable to temporarily hold each character from the string as it is being manipulated:

cCharacter = SUBSTR(cTest, nCnt, 1)

Tip   It’s often better to store the result of a calculation, evaluation, or function to a variable. You can then manipulate the variable without having to repeat the calculation or evaluation.

The following line of code can be used each time a number is encountered to add the number to the second string:

cNumOnly = cNumOnly + cCharacter

The program so far is:

cNumOnly = ""
FOR nCnt = 1 TO 14
   cCharacter = SUBSTR(cTest, nCnt, 1)
   IF ISDIGIT(cCharacter)
      cNumOnly = cNumOnly + cCharacter
   ENDIF
ENDFOR

Testing the Pieces

If you add a couple of commands to the end to display the strings, and then run the program, you can see that the program works with the test string:

cNumOnly = ""
FOR nCnt = 1 TO 14
   cCharacter = SUBSTR(cTest, nCnt, 1)
   IF ISDIGIT(cCharacter)
      cNumOnly = cNumOnly + cCharacter
   ENDIF
ENDFOR
? cTest
? cNumOnly

Output

123-456-7 89 0
1234567890

The output looks correct. But if you change the test string as you are testing the pieces, you can run into problems. Type the following command in the Command window and run the program again:

cTest = "456-789 22"

The program generates an error message. The FOR loop tried to execute 14 times, but there were only 10 characters in the string. You need a way to adjust for varying string lengths. Use LEN( ) to return the number of characters in a string. If you substitute this command in the FOR loop, you’ll find that the program works correctly with both test strings:

cNumOnly = ""
FOR nCnt = 1 TO LEN(cTest)
   cCharacter = SUBSTR(cTest, nCnt, 1)
   IF ISDIGIT(cCharacter)
      cNumOnly = cNumOnly + cCharacter
   ENDIF
ENDFOR
? cTest
? cNumOnly

Putting the Pieces Together

To complete the programming solution for this problem, you might want to switch to reading your data from a table. When you have selected a table to use, scan the records in it and apply your program code to a field in the table rather than to a variable.

First, you could create a temporary table containing a variety of sample strings. Such a table could contain a single character field called TestField and four or five records:

TestField Contents
123-456-7 89 0 -9221 9220 94321 99-
456-789 22 000001 98-99-234

When you substitute the name of the field for the name of the test string, the program looks like this:

FOR nCnt = 1 TO LEN(TestField)
   cCharacter = SUBSTR(TestField, nCnt, 1)
   IF ISDIGIT(cCharacter)
      cNumOnly = cNumOnly + cCharacter
   ENDIF
ENDFOR
? TestField
? cNumOnly

You can manually adjust the record pointer by browsing the table and scrolling through it. When the record pointer is on each of the records, the program works the way you want it to. Or, you can now wrap table navigation code around the rest of your program:

SCAN
   cNumOnly = ""
   FOR nCnt = 1 TO LEN(TestField)
      cCharacter = SUBSTR(TestField, nCnt, 1)
      IF ISDIGIT(cCharacter)
         cNumOnly = cNumOnly + cCharacter
      ENDIF
   ENDFOR
? TestField
? cNumOnly
?
ENDSCAN

Output

123-456-7 89 0
1234567890

456-789 22
45678922

 -9221 9220 94321 99-
922192209432199

000001 98-99-234
0000019899234

Testing the Whole Program

Instead of printing the string at the end of the program, you want to save it in your table. Use the following line of code to do this:

REPLACE TestField WITH cNumOnly

The complete program becomes:

SCAN
   cNumOnly = ""
   FOR nCnt = 1 TO LEN(TestField)
      cCharacter = SUBSTR(TestField, nCnt, 1)
      IF ISDIGIT(cCharacter)
         cNumOnly = cNumOnly + cCharacter
      ENDIF
   ENDFOR
   REPLACE TestField WITH cNumOnly
ENDSCAN

When you have finished the complete program, you need to test it on the sample data before trying it on your real data.

Making the Program More Robust

A robust program does what you want it to, but it also anticipates and deals with possible things that could go wrong. The example program does what you want it to do, but it makes some assumptions that must be true if the program is to work:

If the table isn’t open in the current work area or if the table doesn’t have a character field with the expected name, the program will generate an error message and fail to accomplish the task.

Program to Remove the Non-Numeric Characters from a Field for All Records

Code Comments
lFieldOK = .F.
This variable determines if the necessary conditions exist for the program to work. Initially, set the variable to false (.F.) to assume that the necessary conditions do not exist.
FOR nCnt = 1 TO FCOUNT( )
   IF FIELD(nCnt) = ;
     UPPER("TestField")
      IF TYPE("TestField") = "C"
         lFieldOK = .T.
      ENDIF
      EXIT
   ENDIF
ENDFOR
This section of code goes through every field in the current table until it finds a character field named TestField. As soon as the correct field is found, lFieldOK is set to true (.T.) and EXIT ends the loop (there is no reason to keep checking after the correct field is identified). If no field matches the criteria, lFieldOK remains false (.F.).
IF lFieldOK
The conversion section of the program is executed only if a character field named TestField is present in the currently active table.
SCAN
   cNumOnly = ""
   FOR nCnt = 1 TO LEN(TestField)
     cCharacter = ;
    SUBSTR(TestField, nCnt, 1)
     IF ISDIGIT(cCharacter)
     cNumOnly = cNumOnly + ;
     cCharacter
     ENDIF
   ENDFOR
The conversion code.
   REPLACE TestField WITH ;
  cNumOnly
ENDSCAN
ENDIF
End of the IF lFieldOK condition.

The most limiting feature of this program is that you can use it for only one field. If you want to remove the non-numeric characters from a field other than TestField, you have to go through the program and change every occurrence of TestField to the name of the other field.

Converting the program to a function, as explained in the following sections, allows you to make the code you have written more generic and more reusable, saving you work later.

Using Procedures and User-Defined Functions

Procedures and functions allow you to keep commonly-used code in a single place and call it throughout your application whenever you need it. This makes your code easier to read and easier to maintain because a change can be made once in the procedure rather than multiple times in your programs.

In Visual FoxPro, procedures look like this:

PROCEDURE myproc
  * This is a comment, but it could be executable code
ENDPROC

Traditionally, procedures contain code that you write to perform an operation, and functions do some operations and return a value. In Visual FoxPro, functions are similar to procedures:

FUNCTION myfunc
  * This is a comment, but it could be executable code
ENDFUNC

You can include procedures and functions in a separate program file or at the end of a program file that contains normal program code. You cannot have normal executable program code included in a program file following procedures and functions.

If you include your procedures and functions in a separate program file, you can make these procedures and functions accessible in your program by using the SET PROCEDURE TO command. For example, for a file called FUNPROC.PRG, use this command in the Command window:

SET PROCEDURE TO funproc.prg

Calling a Procedure or Function

There are two ways to call a procedure or function in your programs:

Each of these methods can be expanded by sending or receiving values from the procedure or function.

Sending Values to a Procedure or Function

To send values to procedures or functions, you include parameters. The following procedure, for example, accepts a single parameter:

PROCEDURE myproc( cString )
   * The following line displays a message
   MESSAGEBOX ("myproc" + cString)
ENDPROC

Note   Including the parameters inside parentheses in a procedure or function definition line, for example, PROCEDURE myproc(cString), indicates that the parameter is scoped locally to the procedure or function. You can also allow a function or procedure to accept locally scoped parameters with LPARAMETERS.

Parameters work identically in a function. To send a value as a parameter to this procedure or to a function, you can use a string or a variable that contains a string, as shown in the following table.

Passing Parameters

Code Comments
DO myproc WITH cTestString
DO myproc WITH "test string"
Calls a procedure and passes a literal string or character variable.
myfunc("test string")
myfunc( cTestString )
Calls a function and passes a copy of a character variable or literal string.

Note   If you call a procedure or function without using the DO command, the UDFPARMS setting controls how parameters are passed. By default, UDFPARMS is set to VALUE, so copies of the parameters are passed. When you use DO, the actual parameter is used (the parameter is passed by reference), and any changes within the procedure or function are reflected in the original data, regardless of the setting of UDFPARMS.

You can send multiple values to a procedure or function by separating them with commas. For example, the following procedure expects three parameters: a date, a character string, and a number.

PROCEDURE myproc( dDate, cString, nTimesToPrint )
   FOR nCnt = 1 to nTimesToPrint
      ? DTOC(dDate) + " " + cString + " " + STR(nCnt)
   ENDFOR
ENDPROC

You could call this procedure with this line of code:

DO myproc WITH DATE(), "Hello World", 10

Receiving Values from a Function

The default return value is true (.T.), but you can use the RETURN command to return any value. For example, the following function returns a date that is two weeks later than date passed to it as a parameter.

FUNCTION plus2weeks
PARAMETERS dDate
   RETURN dDate + 14
ENDFUNC

The following line of code stores the value returned from this function to a variable:

dDeadLine = plus2weeks(DATE())

The following table lists the ways you can store or display values returned from a function:

Manipulating Return Values

Code Comments
var = myfunc( )
Stores the value returned by the function to a variable.
? myfunc( )
Prints the value returned by the function in the active output window.

Verifying Parameters in a Procedure or Function

It’s a good idea to verify that the parameters sent to your procedure or function are what you expect to receive. You can use the TYPE( ) and PARAMETERS( ) functions to verify the type and number of parameters sent to your procedure or function.

The example in the previous section, for instance, needs to receive a Date type parameter. You can use the TYPE( ) function to make sure the value your function receives is the right type.

FUNCTION plus2weeks( dDate )
   IF TYPE("dDate") = "D"
      RETURN dDate + 14
   ELSE
      MESSAGEBOX( "You must pass a date!" )
      RETURN { - - }      && Return an empty date
   ENDIF
ENDFUNC

If a procedure expects fewer parameters than it receives, Visual FoxPro generates an error message. For example, if you listed two parameters, but you call the procedure with three parameters, you’ll get an error message. But if a procedure expects more parameters than it receives, the additional parameters are simply initialized to false (.F.). Because there is no way to tell whether the last parameter was set to false (.F.) or omitted, the following procedure checks to make sure the appropriate number of parameters was sent:

PROCEDURE SaveValue( cStoreTo, cNewVal, lIsInTable )
   IF PARAMETERS( ) < 3
      MESSAGEBOX( "Too few parameters passed." )
      RETURN .F.
   ENDIF
   IF lIsInTable
      REPLACE (cStoreTo) WITH (cNewVal)
   ELSE
      &cStoreTo = cNewVal
   ENDIF
   RETURN .T.
ENDPROC

Converting the NUMONLY Program to a Function

NUMONLY.PRG, the example program discussed earlier in The Process of Programming section, can be made more robust and useful by creating a function for the part of the program that removes the non-numeric characters from a string.

Sample Procedure to Return Numeric Characters from a String

Code Comments
FUNCTION NumbersOnly( cMixedVal )
Start of the function, which accepts a character string.
  cNumOnly = ""
  FOR nCnt = 1 TO LEN(cMixedVal)
   cCharacter = ;
  SUBSTR(cMixedVal, nCnt, 1)
    IF ISDIGIT(cCharacter)
       cNumOnly = ;
   cNumOnly + cCharacter
    ENDIF
   ENDFOR
Create a string that has only the numeric characters from the original string.
RETURN cNumOnly
Return the string that has only numeric characters.
ENDFUNC
End of the function.

In addition to allowing you to use this code in multiple situations, this function makes the program easier to read:

SCAN
   REPLACE FieldName WITH NumbersOnly(FieldName)
ENDSCAN

Or, even more simply:

REPLACE ALL FieldName WITH NumbersOnly(FieldName)

Moving Forward

Procedural programming, together with object-oriented programming and the Visual FoxPro design tools, can help you develop a versatile Visual FoxPro application. The rest of this book addresses topics you’ll encounter as you develop Visual FoxPro applications.

For more information on programming with an object-oriented approach, see Chapter 3, Object-Oriented Programming, To learn about designing forms with the Form Designer, see Chapter 9, Creating Forms.