isual FoxPro 8 includes numerous new features that are a direct response to the requests of VFP developers. Just reading through the “What’s New” section of the documentation will take you quite a while due to large quantity of additional or changed features and commands. Not only does the documentation of new features in VFP 8 take up a large number of pages, but there are more than 100 new keywords added to the language. These represent additional Commands, Functions, Class names, Properties, and Methods.
You will have to explore VFP 8 on your own to discover many of the great new additions, but I will give you a quick tour of some of the new feature highlights that have caught my attention.
The Insert-SQL command has gained two great new capabilities. It can now accept data that comes from an object or from a Select-SQL statement. For example, if you have an object (very likely a business object) that among its PEMs has properties that match fields in a cursor, you can insert data into the cursor right from the object. The syntax is very trivial:
Insert into curProducts from name oProduct
Note that oProduct is the name of the business object that has the data. This feature lets you avoid the need to append a blank record to the cursor and then fill the fields with the gather command, as in:
Append Blank in curProducts Gather from name oProduct
Here goes the other new feature: instead of having to get a result set from a query and scan through it populating a cursor, now you can do all at once:
Insert into curProducts ; (ProductID, ProductName, UnitPrice); Select ProductID, ProductName, UnitPrice ; from Products Where UnitInStock > 20
An “Empty” Object?
For a long time, developers have been looking for a lightweight class, mainly when they have to add and remove properties to an object on the fly. Some people have been using the Session or the Custom classes, but those are not really intended to be lightweight classes. Others use classes like Relation or Line, but that is kind of weird from an OOP point of view, because a Relation class is meant to abstract relations between tables, while a Line class is meant to be a visual line in a UI control. The Empty class enters the picture.
The Empty class does not have any intrinsic properties, methods or events (yes, that’s why it is called empty). Not having any members, this class gets instantiated and destroyed very quickly. Of course, this class is useless as is, because if it doesn’t have an AddProperty method like all the base classes in VFP, how can we make any use of it?
The answer is two more new features: the AddProperty and RemoveProperty functions. Despite the fact that these functions can work with any object, their biggest role is to support the Empty class. The use of these functions and the Empty class is very trivial:
oCustomer = CreateObject("Empty") AddProperty(oMonths, "LastName") AddProperty(oMonths, "FirstName") oCustomer.LastName = "Lassala" ? oCustomer.LastName
For removing a property, it goes like this:
Another use for the Empty class comes with using the Scatter command. The Scatter Name oObject command creates an object that has one property for each field in the cursor or table in the current work area, storing the values of the fields into the properties in the brand new object.
But, now we can create an Empty object beforehand, which has some additional properties besides the ones that match with the fields in the cursor, and then use the new ADDITIVE clause to the Scatter command, which will cause the use of an existing object, instead of a brand new one.
For example, if we still have in memory the oCustomer object for the previous sample, we can scatter fields from the customer table to it like this:
Use Customer Scatter Name oCustomer Additive
An Empty class is a sealed class, which means that it cannot be sub-classed.
Putting Some New Features Together
A practical use for an Empty object is getting rid of some of those public variables (or private variables in the main program) that some developers have spread through their applications, especially when they’re not willing to move to an entirely OOP approach.
For example, usually some developers have a few public variables that store environment information, like the name of the Machine, the OS version, the user’s name, and so on. This is a simple sample of how they could get rid of a few of those variables by using an Empty object:
Private poApp *-- We create an "empty" object. poApp = CreateObject("Empty") *-- We add a few properties to the object. AddProperty(poApp, "MachineName",; Substr(Sys(0),1,At("#",Sys(0))-1)) AddProperty(poApp, "OSVersion", Os(1)) AddProperty(poApp, "UserName",; Substr(Sys(0),At("#",Sys(0))+1))
You can see that we create just a single property that will store a reference to an Empty object, and then we use the new AddProperty function to add a few properties to the object (MachineName, OSVersion and UserName). We are using the AddProperty function to also initialize the values of the properties, but we could assign values to the properties in an Object.Property approach, like:
poApp.OSVersion = Os(1)
Then, you can use this object anywhere:
MessageBox("User Name: "+poApp.UserName+Chr(13)+; "Machine Name: "+poApp.MachineName+Chr(13)+; "OS Version: "+poApp.OSVersion)
Let’s now see a sample that plays with the new features both in the Insert-SQL and the Scatter commands. Often, we need to keep track of the size and location of forms as they were the last time they were destroyed (the user runs a form, changes its size and location, then closes the form). The next time the user runs the form, he expects that it will be shown the same way. Doing that now is a piece of cake. Basically, we need a simple table that has numeric fields for keeping the size of a form and its position (Top, Left, Width, and Height). In the Destroy event method of the form, we put this line of code:
Insert into Settings from name Thisform
Of course, we need to apply some logic to check whether we already have a record in the table keeping track of these values. If that’s the case, we can just update the record with this line of code:
Gather name Thisform
Both lines of code will take the Thisform reference to the form object, and then grab the value of its Top, Left, Width and Height properties to replace in the table.
Now, in the Init event method, we grab the values stored in the table and then update our object properties with this line of code:
Scatter name Thisform additive
DataEnvironment Class and Builder
Let’s picture a common situation: you have a form with a beautiful (read “complex”) DataEnvironment defined, with lots of tables, relations, ordering, and so forth. Then you face a new need where you’ll have another form that requires the same settings of the first one. Until VFP 7, you had two choices: recreate everything again from scratch manually or create the DataEnvironment programmatically. That was because we weren’t able to define and subclass the DataEnvironment class. Now we are!
Now, with VFP 8, we can just go to the form that has the DataEnvironment defined, and then select Save As Class from the File menu. In the dialog box you’ll find a new “DataEnvironment” option. Then, it’s just a matter of typing the name of the class, where it should be stored and a description for it (see Figure 1)
There is also a minor enhancement for the indexes of a table: a collating sequence can be defined individually for every single index tag.
Another very neat new feature is that an expression can be defined as the Caption of a field in the Table Designer. This expression is evaluated every time a Browse window is opened. However, there’s something much cooler about this. In earlier versions, we could drag a field from a DataEnvironment and drop it onto a form, and that would create a label with its Caption property set to the caption of the field.
In VFP 8, the caption of the label will be evaluated at run-time, which gives us the flexibility for data-driven labels in our forms. For example, I could set the Caption of the CompanyName field to the following expression:
“Localize” is just a Stored Procedure I have that will return the localized version (in this sample, the Portuguese version) of “Company Name”, according to the Regional Settings running on this computer (the implementation doesn’t matter here). In Figure 3, you see the label that’s evaluated every time the form runs.