Back

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.

Dataenvironment.Init

 

Dataenvironment.OpenTables

If the AutoOpenTables property is set to .T.

Dataenvironment.BeforeOpenTables

If the AutoOpenTables property is set to .T.

Form.Load

 

Controls are created

 

Form.Init

 

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

THISFORM.DataEnvironment.CloseTables()

LOCAL laCursors(1), lnCnt, lcCursor, lcTable

 

* Get a list of all of the objects in the DataEnvironment into an array

AMEMBERS(laCursors,THISFORM.DataEnvironment, 2)

WITH THISFORM.DataEnvironment

  * 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,;

                  RAT("\",.&lcCursor..Database)+1,;

                  LEN(.&lcCursor..Database)-;

                  RAT("\",.&lcCursor..Database))

        .&lcCursor..Database = oApp.cDataLocation + lcTable

      ELSE

               * 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,;

                  RAT("\",.&lcCursor..Cursorsource)+1,;

                  LEN(.&lcCursor..Cursorsource)-;

                  RAT("\",.&lcCursor..Cursorsource))

        * Next set the cursorsource property to point to the path stored in

        * the oApp object

        .&lcCursor..CursorSource = oApp.cDataLocation + lcTable

      ENDIF

    ENDIF

  ENDFOR

ENDWITH

* Finally, open the tables with their new path

THISFORM.DataEnvironment.OpenTables()

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.

Summary

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.