HOWTO: Encapsulate Data in the Form Builder

ID: Q131027


The information in this article applies to:
  • Microsoft Visual FoxPro for Windows, version 3.0


SUMMARY

In a true object-oriented programming paradigm, data used in a form is encapsulated from the underlying form. This hides the names of the tables and fields from the form. By using this approach, a table or field name could change before the form is run, and the form should still function properly. As a result, the form does not need to know specific information about the data tables. The tables can change independent of any form.

This article describes one way to encapsulate data from the form.


MORE INFORMATION

The sample code in this article includes a custom class definition (clMyData) and a subclass (clcustomer) built from this custom class. The program is saved in a file named DATAENC.PRG.

To use this approach, add several lines of code to the form's Init procedure. If the form is part of a formset, place the code in the Init of the formset instead.


   * SET PROCEDURE to file containing class definition

      IF !"DATAENC"$ SET("PROCEDURE")
      SET PROCEDURE TO dataenc ADDITIVE
   ENDIF

   * Declare public variables to hold instantiations of custom classes
   PUBLIC oMyData, oCustomer

   * Instantiate each class
   oMyData = CREATEOBJECT("clMyData")
   oCustomer = CREATEOBJECT("clCustomer") 

Program Logic

The custom class (clMyData) examines the form being loaded. It uses the form's control array to itemize all of the controls on the form. If a control is a container (for example, a command button group or a page frame), the program itemizes all controls within the container as well.

After identifying all controls on the form, the program replaces the original rowsource property specified when the form was designed. The replacement value for each control is specified in the subclass (clCustomer).

A subclass similar to clCustomer is created for each table used in the form. The subclass method DIFFERS creates a two-column array, with one row for each field contained in the table. The first column of the array contains the design-time rowsource. The second column of the array contains the run-time rowsource, usually the field name from a table.

This array is passed to the parent class and used in the GETDIFFERS method to dynamically change the rowsource properties of all applicable controls at runtime. The form file on disk is never changed.

All method code required to do the conversion is included in the parent class. Only one method in the subclass needs to be edited to add an additional table to the form.

The class listed in this article is a simple example. It could easily be expanded to handle additional functionality, such as building select statements for list boxes.

Sample Code


   *******  Begin code example

   ************************************************************************
   * Class Name   : clMyData
   * Parent Class : Custom
   * Subclasses   :
   ************************************************************************

   Define Class clMyData as Custom

   ************************************************************************
   *   Properties
   ************************************************************************

      lIsFormSet = .t. && logical - is this a formset?
      nNumberofForms=1    && number of forms
      flag = .f.

   ************************************************************************

   PROCEDURE OpenTables

      Parameters pDBC, pTable
      * Called from each subclass to open needed dbcs and dbfs
      * Set buffering
      SET MULTILOCKS ON

      * Open tables
      IF !DBUSED(pDBC)
         OPEN DATA (pDBC)
      ENDIF
      IF !USED(pTable)
         USE (pTable) IN 0
      ELSE
         SELECT (pTable)
      ENDIF
      =CURSORSETPROP("Buffering",5,(pTable))

      * Populate first object
      THIS.GETINFO(pTable, @pArray)

   ************************************************************************
   * This is called once in each subclass to create a unique array name

   PROCEDURE DIFFERS

      PARAMETERS pTable
      aDiffers=SYS(2015)
      PUBLIC &adiffers(1,2)
      RETURN aDiffers

      * Place control source property for form in first column
      * Place  fields which make this field in second column

      *********************************************************************
   * The following procedure is called by the subclass one time for each
   * field in the data table

   PROCEDURE POPDIFFERS
   PARAMETERS pArrayName, pControlSource,  pInputExpr

      iCurrentRow = ALEN((pArrayName),1)
      cArrayName = pArrayName + "(iCurrentRow,"
      &cArrayName.1) = pControlSource
      &cArrayName.2) = pInputType

      nNewSize  = iCurrentRow + 1
      DIMENSION &pArrayName.(nNewsize, 2)
   ************************************************************************

   PROCEDURE INIT

      * Figure out if part of a formset by trying to generate
      * an error number 1938 and setting HighestClass appropriately

      temp = thisformset.name

      * Cycle through all of the controls on the current form and find the
      * current control source where needed.
      * Then, the init of each subclass will refer to this cursor later on
      * to update the control source properties

      CREATE CURSOR curMyProps (cName c(75), source c(75))
      CREATE CURSOR curContainers (cName c(75))
      IF lIsFormset
      nNumberofForms = _screen.FORMCOUNT
         FOR i = 1 to nNumberofForms
            HighestClass = "_SCREEN.FORMS(" + alltrim(str(i)) +")"
            this.allforms(highestclass)
         NEXT i
      ELSE
         HIGHESTClass = "THISFORM"
         THIS.AllForms(HighestClass)
      ENDIF

   ENDPROC

   ************************************************************************
   * The following procedure is executed 1 time for each form
   * by the class init method

   PROCEDURE AllForms
   PARAMETERS HighestClass
      iNumControls = &highestclass..controlcount
      this.enumerate(HighestClass, iNumControls)
      DO WHILE .T.
         SELECT * from curContainers into CURSOR TEMP
         IF _tally = 0
            EXIT
         ENDIF
            SET SAFETY OFF
            CREATE CURSOR curContainers (cName c(75))
            SELECT TEMP
         SCAN
            HighestClass = alltrim(cName)
            iNumControls = &HighestClass..ControlCount
            this.enumerate(HighestClass, iNumControls)
         ENDSCAN
      ENDDO

   ENDPROC

   ************************************************************************
   * The following procedure figures out which controls exist on the
   * container

   PROCEDURE enumerate
   Parameters HighestClass, iNumControls

      FOR i = 1 TO iNumControls
         IF &highestclass..baseclass != "Commandgroup"
             cCurControlBase = &highestclass..controls(i).baseclass
             cCurControl = UPPER(highestclass + "." + ;
                &highestclass..controls(i).name)
             cCurControlSource = ;
                UPPER(&highestclass..controls(i).controlsource)
          ELSE
            cCurControlBase = &highestclass..buttons(i).baseclass
            cCurControl = UPPER(highestclass + "." + ;
                &highestclass..buttons(i).name)
             cCurControlSource = ;
                UPPER(&highestclass..buttons(i).controlsource)
          ENDIF
         IF  cCurControlBase = "Textbox" ;
            OR cCurControlBase = "Check box" ;
            OR cCurControlBase = "Commandbutton" ;
            OR cCurControlBase = "Optionbutton" ;
            OR cCurControlBase = "Spinner" ;
            OR cCurControlBase = "Column" ;
            OR cCurControlBase = "EditBox"
            SELECT CurMyProps
            APPEND BLANK
            REPLACE cName WITH cCurControl
            REPLACE source WITH cCurControlSource
         ELSE
            IF cCurControlBase = "Page" ;
               OR cCurControlBase = "PageFrame";
               OR cCurControlBase = "Form" ;
               OR cCurControlBase = "Grid" ;
               OR cCurControlBase = "Toolbar"

               SELECT curContainers
               APPEND BLANK
               REPLACE cName with cCurControl
            ENDIF
         ENDIF
      NEXT i

   ENDPROC

   ************************************************************************
   * This procedure changes the rowsource in the cursor

   PROCEDURE UpdateCursor
   Parameters pArray, cTableName

      SELECT curMyProps
      =cursorsetprop("Buffering",1,(cTableName))
      FOR i = 1 TO ALEN((pArray),1)
         cNewSource = &pArray.(i,2)
         cOldSource = &pArray.(i,1)
         REPLACE source WITH cNewSource ;
           FOR ALLTRIM(source) = UPPER(cOldSource)
      ENDFOR
      SCAN
         cNewName = alltrim(cName)
         &cNewName..controlsource = alltrim(source)
      ENDSCAN

   ************************************************************************

   PROCEDURE error
   Parameters pErrorNum, p3, p4
      IF pErrorNum = 1938
         lIsFormSet = .F.
      ENDIF

   ************************************************************************

   PROCEDURE destroy
      PARAMETERS pArrayName
      RELEASE (pArrayName)

   ************************************************************************

   ENDDEFINE && End Definition of clMyData

  ************************************************************************
  * END CLASS DEFINITION
  ************************************************************************

   ************************************************************************
   * Class Name   : clCUSTOMER
   * Parent Class : clMyData
   * Subclasses   :
   ************************************************************************

   Define Class clCUSTOMER as clMyData

   ************************************************************************
   *   Properties
   ************************************************************************

      cTableName = "CUSTOMER"  &&Name of the .dbf file
      cDBCName   = "POES"      &&Name of the .DBC file
      cMyArray   = ""

   ************************************************************************

   PROCEDURE INIT
      clMyData::OpenTables(THIS.cDBCName, THIS.cTableName)
      cMyArray = clMyData::Differs()
      clMyData::PopDifferS(cMyArray,"FullName","customer.c_Name")
      clMyData::PopDifferS(cMyArray,"SSN","customer.c_SSN")
      clMyData::PopDifferS(cMyArray,"STREET","customer.c_STREET")
      clMyData::PopDifferS(cMyArray,"CITY","customer.c_CITY")
      clMyData::PopDifferS(cMyArray,"STATE","customer.C_STATE")
      clMyData::PopDifferS(cMyArray,"ZIP","customer.c_ZIP")
      clMyData::PopDifferS(cMyArray,"PHONE","customer.c_PHONE")
      clMyData::PopDifferS(cMyArray,"CCN","customer.c_CCN")
      this.parentPopDifferS(cMyArray,"CCEXP","customer.c_CCEXP")

      clMyData::UpdateCursor(cMyArray, this.cTableName)
      PUBLIC aCustomer(1,2)
      SELECT c_ssn, c_name FROM CUSTOMER INTO ARRAY aCustomer

   ************************************************************************

   ENDDEFINE && End Definition of clCustomer

   ************************************************************************
   * END CLASS DEFINITION
   ************************************************************************
 

Additional query words:

Keywords : kbcode kbOOP
Version :
Platform :
Issue type :


Last Reviewed: August 20, 1999
© 2000 Microsoft Corporation. All rights reserved. Terms of Use.