Difference between revisions of "Visual FoxPro"

From TheAlmightyGuru
Jump to: navigation, search
(Quirks)
(4 intermediate revisions by the same user not shown)
Line 50: Line 50:
 
  String = DateTime()
 
  String = DateTime()
  
Not to mention, this prevents the compiler from identifying type-based errors at design time, so you get a lot of Operator/Operand Type Mismatch errors at runtime. For example, the following code compiles just fine:
+
Not to mention, this prevents the compiler from identifying type-based errors at design time, so you get a lot of "Operator/operand type mismatch" errors during runtime. For example, the following code compiles just fine:
  
 
  Integer = 1
 
  Integer = 1
 
  String = "test"
 
  String = "test"
 
  Sum = Integer + String
 
  Sum = Integer + String
 +
 +
===Uncommon Logical Representation===
 +
In most languages use the words "true" and "false" to represent their values, but VFP uses the first letter surrounded by periods; so, .T. is true and .F. is false. There is also .NULL. for null.
 +
 +
===Non-Exact Comparison With Equals===
 +
By default in VFP, when you compare two strings with an equal sign, VFP only compares to the length of the second string. This yields results different from most programming languages. For example, the following code will return .T.:
 +
 +
MessageBox("Testing" = "Test")
 +
 +
To force VFP to behave like other languages and compare the strings to their full length use a double equals sign. Thus, the following code will return .F.:
 +
 +
MessageBox("Testing" == "Test")
 +
 +
You can also use Set Exact On, which will cause VFP to pad the length of each string with spaces until they are of equal length, but this may yield undesired results as well, for example, the first comparison will be .F., but the second will be .T.:
 +
 +
Set Exact On
 +
MessageBox("Testing" = "Test")
 +
MessageBox("Testing" = "Testing ")
  
 
===VFP Processes Fields Before Variables===
 
===VFP Processes Fields Before Variables===
Line 86: Line 104:
 
  sString = [This is the contents of a string]
 
  sString = [This is the contents of a string]
  
This confuses the color syntaxing of the interpreter, so when you use brackets to denote an element in an array, it is colored as though you're setting a value of a string.
+
This confuses the interpreter's color syntaxing, so, when you use brackets to denote an element in an array, it is colored as though you're setting a value of a string. Since VFP is more BASIC-like, I prefer to use parentheses for my arrays which yields the correct color syntax.
 +
 
 +
===Weak Cursor Support===
 +
VFP uses temporary tables called cursors (ambiguous name) which are extremely useful, but several commands do not work on them. For example, you cannot Pack a cursor, and, to Append From a cursor, you must wrap its alias with DBF().
  
 
===VarType Can't Identify Non-Existent Properties On Non-Existent Objects===
 
===VarType Can't Identify Non-Existent Properties On Non-Existent Objects===
Because VFP uses the period operator for both table fields and object properties, it prevents VarType from being able to properly distinguish a table from a non-existent properties on non-existent objects. For example, in the following code, NotAnObject will properly be described with a "U" for undefined, the oForm.Visible property will be described with an "L" for logical, the oForm.NotAProperty non-existent property will be described with a "U," but the non-existent property on the non-existent object will throw an error of "Alias 'NOTANOBJECT' is not found," because VFP will assume it's a table because it has a period in it, and it's not an object.
+
Because VFP uses the period operator for both table fields and object properties, it prevents VarType from being able to properly distinguish a table from non-existent properties on non-existent objects. For example, in the following code, NotAnObject will properly be described with a "U" for undefined, the oForm.Visible property will be described with an "L" for logical, the oForm.NotAProperty non-existent property will be described with a "U," but the non-existent property on the non-existent object will throw an error of "Alias 'NOTANOBJECT' is not found," because, since it's not an object, VFP will assume it's a table before running VarType.
  
 
  MessageBox(VarType(NotAnObject))
 
  MessageBox(VarType(NotAnObject))
Line 100: Line 121:
  
 
  MessageBox(Type("NotAnObject.NotAProperty"))
 
  MessageBox(Type("NotAnObject.NotAProperty"))
 +
 +
===Can't Close Database When Its In the Open Project===
 +
The command Close Database will properly close a database during run time and in the Command Window during design time. However, if you have a project open that has a database included, running Close Database during design time will not close the database, even if you do not have the project's Data tab selected, but will also not throw an error. To close the database, you must first close the project then issue Close Database, or just use Close All, which will close both.
  
 
===Sporadic Failures On One-Record Tables===
 
===Sporadic Failures On One-Record Tables===
My company found an extremely rare bug that occurs when you run a Locate command on a table with only one record. Even if Found() returns .T. and RecNo() returns 1, VFP is actually on End of File and will return an empty value for the requested field. This can be resolved by adding an additional blank record to that table.
+
My company found an extremely rare bug that occurs when you run a Locate command on a table with only one record. Even if Found() returns .T. and RecNo() returns 1 indicating it is on the first record, VFP is actually on End of File and will return an empty value for any requested field. Or code was something to this effect:
 +
 
 +
Create Table "Settings.dbf" (SettingNo I, SettingName C(4))
 +
Insert Into Settings (SettingNo, SettingName) Values ("Test", 1)
 +
Locate For Settings.SettingNo = 1
 +
If Found() = .T.
 +
    MessageBox(RecNo())
 +
    MessageBox(Settings.Name)
 +
EndIf
 +
 
 +
For the majority of the time, the MessageBoxes would read "1" then "Test", but every so often the second MessageBox would be blank. This can be resolved by adding several blank records to the table.
  
 
==Links==
 
==Links==

Revision as of 10:01, 10 January 2018

Visual FoxPro 9.

Visual FoxPro, or VFP, is a programming language and IDE from Microsoft. The primary use of the language is for rapid database application development. The programming syntax is similar to that of Visual BASIC (not VB.NET). The language uses dynamic inferred structural typing. It compiles to pseudo-code which is interpreted by the runtimes which must be distributed along with the program.

Visual FoxPro is based off of FoxPro, which was the result of Microsoft buying FoxBase from Fox Software. FoxBase was derived from dBase III.

The last official release of Visual FoxPro was version 9, which was released in 2004 with a final service pack released in 2007. Microsoft officially discontinued support in 2015.

Though I had seen FoxPro icons since my first days with Windows 3, I never knew what FoxPro was. I got my first taste of Visual FoxPro in 1999 because it was the main programming language used by the company I started working for. I was familiar with Visual BASIC, so it was pretty easy for me to pickup the syntax, and I had just developed an Access application for my high school, so I knew enough about databases to secure my job. I've used VFP from version 6 until its final release, version 9.0 SP2.

Review

Good

  • The language has a built-in database creator, editor, and viewer. Though it's not without its problems, it's the best I've ever worked with.
  • It has a full SQL interpreter as well as many additional database features not found even in modern databases, and they're very easy to use.
  • The SQL speed is lightning fast, even to the point of outpacing SQL Server at times since it doesn't have built-in transaction logging.
  • The IDE uses a wonderful multi-window system so you can easily open and view several different code blocks at the same time, even from the same object, something I wish Visual Studio would adopt.
  • Some of the table field types are quite useful like the currency with 4-digit decimal precision, and the numeric which is based on length of digits rather than bytes.
  • Unlike most earlier versions of Visual BASIC, VFP had several built-in variable types like datetime and logical.

Bad

  • The database format is way out of date and doesn't support any modern formats like Unicode.
  • Numeric variables (and likewise, table fields) are divorced from most other languages. There is no support for unsigned values at all, and there are no primitive types like byte, int, long, etc. Instead, all numbers are treated as either signed double integers or floats, and even then, their type is implied.
  • Functions that affect file names like Copy File don't preserve text case.
  • The UI tries to remember the position of windows and the code block you last viewed, but usually fails.
  • Although VFP is mostly object oriented, it has a lot of legacy commands and functions that are procedural. This lack of standards creates confusion.
  • While text fields longer than 254 characters are possible, they require the use of "memos" which are especially cumbersome to work with.
  • VFP's help has a poor index which is missing dozens of commands. For example, Type "ALEN" in the Index tab and you get a page for "_ALen() API library routine," but there isn't an entry for the ALEN() Function, even though a page exists and can be found in the Search tab.
  • VFP's help is seriously lacking on SQL examples and only shows you the most basic of queries.

Ugly

  • VFP has really poor ActiveX and OLE support, often to the point of crashing the UI.
  • VFP has really poor array support and only supports 2D arrays (to resemble tables). Single dimensional arrays, or three or more dimensional arrays are not possible.

Quirks

This is a list quirks found in VFP and how to resolve them.

Dynamic Inferred Structural Typing

It's refreshing to have a language intelligent enough to determine what type of variable you need based on how you assign it and not need to cast types every time it changes. For example, the following code will yield "1.1," but in a strong-typed language, you would need to declare the type of Integer as an int, Float as a float, Sum as a float, cast Integer into a float to add them, and then convert to a string to display them with MessaheBox.

Integer = 1
Float = 0.1
Sum = Integer + Float
MessageBox(Sum)

However, this allows for very sloppy code, for example, the following will run just fine:

String = "test"
String = 1
String = .T.
String = DateTime()

Not to mention, this prevents the compiler from identifying type-based errors at design time, so you get a lot of "Operator/operand type mismatch" errors during runtime. For example, the following code compiles just fine:

Integer = 1
String = "test"
Sum = Integer + String

Uncommon Logical Representation

In most languages use the words "true" and "false" to represent their values, but VFP uses the first letter surrounded by periods; so, .T. is true and .F. is false. There is also .NULL. for null.

Non-Exact Comparison With Equals

By default in VFP, when you compare two strings with an equal sign, VFP only compares to the length of the second string. This yields results different from most programming languages. For example, the following code will return .T.:

MessageBox("Testing" = "Test")

To force VFP to behave like other languages and compare the strings to their full length use a double equals sign. Thus, the following code will return .F.:

MessageBox("Testing" == "Test")

You can also use Set Exact On, which will cause VFP to pad the length of each string with spaces until they are of equal length, but this may yield undesired results as well, for example, the first comparison will be .F., but the second will be .T.:

Set Exact On
MessageBox("Testing" = "Test")
MessageBox("Testing" = "Testing ")

VFP Processes Fields Before Variables

When reading or writing data, VFP tries to access table fields before memory variables. This can create unexpected results when you have a memory variable with the same name as field in the currently selected table. For example, in the following code, the MessageBox will read "Table Field" not "Memory Variable":

Create Cursor Test (Variable C(11))
Insert Into Test (Variable) Values ("Table Field")
Variable = "Memory Variable"
MessageBox(Variable)

To force VFP to read and write to a memory variable instead of a field, add "m." to the beginning of the variable. For example, in this code, the MessageBox will read "Memory Variable":

Create Cursor Test (Variable C(11))
Insert Into Test (Variable) Values ("Table Field")
m.Variable = "Memory Variable"
MessageBox(m.Variable)

Personally, I find it to be best practice to never give my variables names that might be used by fields. To guarantee this, I use Hungarian notation for memory variables, but not for fields.

Brackets Are Used For Both Strings and Arrays

Like Visual BASIC, VFP uses parentheses to call an array element, for example:

Local Array aTest(10, 2)

However, VFP also lets you use brackets like a C-based language:

Local Array aTest[10, 2]

But, to really complicate things, VFP also lets you use brackets for strings:

sString = [This is the contents of a string]

This confuses the interpreter's color syntaxing, so, when you use brackets to denote an element in an array, it is colored as though you're setting a value of a string. Since VFP is more BASIC-like, I prefer to use parentheses for my arrays which yields the correct color syntax.

Weak Cursor Support

VFP uses temporary tables called cursors (ambiguous name) which are extremely useful, but several commands do not work on them. For example, you cannot Pack a cursor, and, to Append From a cursor, you must wrap its alias with DBF().

VarType Can't Identify Non-Existent Properties On Non-Existent Objects

Because VFP uses the period operator for both table fields and object properties, it prevents VarType from being able to properly distinguish a table from non-existent properties on non-existent objects. For example, in the following code, NotAnObject will properly be described with a "U" for undefined, the oForm.Visible property will be described with an "L" for logical, the oForm.NotAProperty non-existent property will be described with a "U," but the non-existent property on the non-existent object will throw an error of "Alias 'NOTANOBJECT' is not found," because, since it's not an object, VFP will assume it's a table before running VarType.

MessageBox(VarType(NotAnObject))
oForm = CreateObject("Form")
MessageBox(VarType(oForm.Visible))
MessageBox(VarType(oForm.NotAProperty))
MessageBox(VarType(NotAnObject.NotAProperty))

You can get around this problem by using the Type command when you're not sure if the object will exist:

MessageBox(Type("NotAnObject.NotAProperty"))

Can't Close Database When Its In the Open Project

The command Close Database will properly close a database during run time and in the Command Window during design time. However, if you have a project open that has a database included, running Close Database during design time will not close the database, even if you do not have the project's Data tab selected, but will also not throw an error. To close the database, you must first close the project then issue Close Database, or just use Close All, which will close both.

Sporadic Failures On One-Record Tables

My company found an extremely rare bug that occurs when you run a Locate command on a table with only one record. Even if Found() returns .T. and RecNo() returns 1 indicating it is on the first record, VFP is actually on End of File and will return an empty value for any requested field. Or code was something to this effect:

Create Table "Settings.dbf" (SettingNo I, SettingName C(4))
Insert Into Settings (SettingNo, SettingName) Values ("Test", 1)
Locate For Settings.SettingNo = 1
If Found() = .T.
    MessageBox(RecNo())
    MessageBox(Settings.Name)
EndIf

For the majority of the time, the MessageBoxes would read "1" then "Test", but every so often the second MessageBox would be blank. This can be resolved by adding several blank records to the table.

Links