Originally published in FoxTALK in 1998
Using Data From Different Places
The client says they want to have multiple data locations for different sets of data. You say sure thatís easy and set out to create the application. You use the form designer to build the forms and use the data environment to make the data binding easy. You test the system and find out that Visual FoxPro has hard coded the location of the data files on you. What do you do? How can you have the data location be determined at runtime?
Where is the data location stored?
Using the property sheet to look at the objects in the formís data environment, you will find that each Cursor object has a database property. This database property stores the database that the cursor belongs to (including the path to that database). It is the database property that fixes the location of the data.
For tables in a database and for views the property CursorSource stores the name of the table or view. With free tales the CursorSource also stores the path to that table. To redirect the data to another location you need to change the value of either the database or the CursorSource property so they point to the directory you want.
Where to set the data location
If you are to succeed in changing the location of the data tables and views, you need to make the change before any controls in the form have been bound to their data. The firing sequence of a form loading is shown in the following table.
You could write your code in the Dataenvironmentís Init event, but that would require repeating the code in every form you make. The ideal candidate is the formís Load event, as it fires before the controls are created and after the Dataenvironment is created. Also, by using the formís Load you can put the code into a form class and use that class to create your forms thus eliminating the need to repeat the code in every form you make.
Where to store the data location
The first thought might be to use public or global memory variables, but that violates a number of good programming practices. The best candidate for storing the data location is to use one of the manager objects in the framework for your applications.
In the example I used a class named AppClass which is subclassed from the Visual FoxPro custom baseclass. To this class I added a property, cDataLocation, to store the path to the data tables. There is no other code in the AppClass, in a real system there most likely would be a lot more code in that class but the code is not relevant to what we are doing right now.
Finally, some code
Below is listed the code I wrote in the form classí Load event.
* Close the tables
LOCAL laCursors(1), lnCnt, lcCursor, lcTable
* Get a list of all of the objects in the DataEnvironment into an array
* Work through the array
FOR lnCnt = 1 TO ALEN(laCursors,1)
* Get the name for the current object
lcCursor = laCursors(lnCnt)
* Check to see if this object is a cursor
IF .&lcCursor..BaseClass = "Cursor"
* Check to find out if the table or view is associated with a database
IF NOT "\" $ .&lcCursor..Cursorsource
* If it is associated with a database set the Database property
* First get the Databaseís name out of the current cursorsource property setting
lcTable = SUBSTR(.&lcCursor..Database,;
.&lcCursor..Database = oApp.cDataLocation + lcTable
* If it is not associated with a database set the cursorsource property
* First get the tableís name out of the current cursorsource property setting
lcTable = SUBSTR(.&lcCursor..CursorSource,;
* Next set the cursorsource property to point to the path stored in
* the oApp object
.&lcCursor..CursorSource = oApp.cDataLocation + lcTable
* Finally, open the tables with their new path
The comments in the above code are fairly extensive, so I will only discuss the points that may not be clear. The first question is why do I call the CloseTables method before doing any of this stuff? Couldnít you just set the AutoOpenTables property for the Dataenvironment to .F.?
In order to mess around with the Database and CursorSource properties of the cursor objects it is necessary that the tables be closed. Yes, you could set AutoOpenTables to .F., but if the code depended on this setting (that is it did not explicitly close the tables) then it would fail if any form ever had its AutoOpenTables set to .T. By calling the CloseTables method I removed that dependency.
Before this form class is instantiated it is necessary to create the oApp object so the form can find its cDataLocation property. Although this is an external dependency of the form class, it is an acceptable one because it represents a contract of the application framework. The AppClass could be modified to look up the data path during its Init and a method could be provided for changing the cDataLocation while the application is running.
Testing it all
To test this you can put the code into a formís load. Use the visual data environment to put tables into the form from a particular directory. Make a copy of those same tables in another directory. Set the cDataLocation property of the AppClass to point to the second directory. Run the form and change some data. Now browse the two copies of the table and see which one got edited.
This exercise has value in and of itself. Using different data locations is often a requirement for systems. There may be a need to handle data for multiple companies, or to handle switching from a tutorial directory to the real system data. This approach is a good start at accomplishing these requirements.
The code also shows some other issues related to reducing the number of dependencies in the system design, manipulating a dataenvironment at runtime, using classes to reduce coding requirements, and the use of an application object.