Session E-WIZ

Advanced Builder/
Wizard Construction

Rod Paddock
Dash Point Software, Inc


Introduction

When Microsoft developed Visual FoxPro they realized that the integrated development environment (IDE) supplied with Visual FoxPro may not have all of the features that developers might need over the long term. Upon this realization they decided to open up the development environment to FoxPro developers to add their own features. They did this by incorporating some features that allow FoxPro developers to call FoxPro applications and programs from the IDE. These tools are commonly known as builders. Builders are FoxPro programs that you can use to :

Having this ability can greatly improve your development process. Along with the ability to extend the Visual FoxPro IDE Microsoft made Visual FoxPro object oriented. This allows you to create and reuse your own form components. What this means is you no longer have to reinvent the wheel for each and every application. Now picture this: you can create a builder to add and manipulate your own controls in the FoxPro IDE. In other words: If you have spent the time in developing an effective development environment you can automate large portions of your development process.

In this session you will learn:

Basic Builder Construction

Just like any new thing your first steps are often the most crucial. This is also true when it comes to creating builders. When you create your own builders you need to create an application that substitutes for the BUILDER.APP application included with Visual FoxPro. This application must be able to accept two parameters. The first is a parameter that will be used to reference the object you wish to manipulate, the second is a character parameter that specifies how the builder was called. Builders can be called from many different areas of the Visual FoxPro IDE. You can call builders by:

The Builder Creation ToolKit

Creating builders requires knowledge of a specific set of functions. You will use a small set of Visual FoxPro a majority of the time when you are creating builders. This next section demonstrates the use of some of these functions.

_BUILDER

The first item in the Builder Creation Toolkit is to have an understanding of the Visual FoxPro system variable: _BUILDER. The _BUILDER system variable allows you to specify a program to be called whenever a builder is activated from the Visual FoxPro IDE. This program needs to accept two parameters. The first is a handle to the object selected when the builder was created the second is the mechanism that was used to call the builder program. Some of the potential parameters for the second parameter are:

TOOLBOX Called from the Visual FoxPro toolbar
RTMOUSE Called by selecting Builder… from the rightmouse popup menu.
QFORM Called by the quick form option on the menu
RI Called by the referential integrity builder.

The following code shows how to determine the source of a builder call

Lparameters poObjectHandle, pcBuilderSource
Do Case

Case pcBuilderSource = "TOOLBOX"
Messagebox("Called from toolbox") && notice the function doesn't need an = anymore
Case pcBuilderSource = "RTMOUSE"
Messagebox("Called from rightmouse") && notice the function doesn't need an = anymore
Case pcBuilderSource = "QFORM"
Messagebox("Called from quickform") && notice the function doesn't need an = anymore
Case pcBuilderSource = "RI"
Messagebox("Called from the Database Container") && notice the function doesn't need an = anymore
Endcase

ASELOBJ()

Where calling your own program with the _BUILDER system variable is one method of creating a builder. There is another useful function you can use. This function is ASELOBJ(). The ASELOBJ() function creates an array of objects selected in one of the Visual FoxPro design surfaces. Where the program specified by the _BUILDER system variable can accept an object reference it is limited to only one object reference. You need to use the ASELOBJ() function in cooperation with the parameter passed to your wizard to determine the number of objects to be dealt with. The following code demonstrates using the _BUILDER method of object selection along with the ASELOBJ() function.

Lparameters poObjectHandle, pcBuilderSource
Do Case
Case pcBuilderSource = "TOOLBOX"
Messagebox("Called from toolbox") && notice the function doesn't need an = anymore
Case pcBuilderSource = "RTMOUSE"
Messagebox("Called from rightmouse") && notice the function doesn't need an = anymore
Case pcBuilderSource = "QFORM"
Messagebox("Called from quickform") && notice the function doesn't need an = anymore
Case pcBuilderSource = "RI"
Messagebox("Called from the Database Container") && notice the function doesn't need an = anymore
Endcase

*-- Determine how many objects were passed in:
Local lnSelectedObjects
lnSelectedObjects = Aselobj(laObjects)
Messagebox("There are " + Str(lnSelectedObjects) + " objects Selected")

Aselobj has an optional parameter. The optional parameter allows you to create an array with the handle to the objects container by passing a 1, or you can select the forms Data Environment by passing a 2.

READMETHOD() and WRITEMETHOD()

The Readmethod() and WriteMethod() methods allow you to read and write code into objects passed to a builder. The following code demonstrates reading and writing code using the Readmethod() and WriteMethod() methods. The ReadMethod method requires one parameter: The name of the method to read from. The WriteMethod() method requires two parameters the name of the method to write to and the expression to write to that method. The following form demonstrated the code for a method builder.

*-- The following code demonstrates the code for the program specified by the _builder system variable.
Lparameters poObjectHandle, pcBuilderSource
DO FORM testbld2 WITH poObjectHandle

The TESTBLD2 form has the following code in it:

*-- Form.Init()
Lparameters poObjectHandle

ThisForm.oObjectHandle = poObjectHandle
Thisform.edit1.Value = Thisform.oObjectHandle.readmethod("Init")
Thisform.edit2.Value = Thisform.oObjectHandle.readmethod("Init")

*-- CommandButton1.Click()
Thisform.oObjectHandle.WriteMethod("Init",Thisform.edit2.Value)
Thisform.Release()

*-- CommandButton2.Click()
Thisform.Release()

READEXPRESSION() and WRITEXPRESSION()

Accompanying the ReadMethod and WriteMethod() are two other methods. ReadExpression() and WriteExpression(). Where the two method function read and write methods these function allow you to alter expressions found in properties.

ADDOBJECT() and REMOVEOBJECT()

Two common functions you will use when creating builders are ADD and REMOVE object. These two methods allow you to add and remove objects dynamically. The use of these two functions will be demonstrated below.

Constructing Your First Builder

Earlier in this session you learned how to create some very basic builder components. Now you can create a builder of your own.

FUNCTION MyBuild
LPARAMETERS poObjectReference, pcCalledBy

*-- Lets display the name of the object and
*-- where we were called from
=MESSAGEBOX("Object Name[" + poObjectReference.name + ;
"] Called By [" + pcCalledBy + "]")

Now that you have created this most basic of builders you need to substitute Visual FoxPro's default builder with your own. You do this by setting the Visual FoxPro _BUILDER system variable to your program. You can do this by typing _BUILDER = "MYBUILD.PRG" in the Command Window. Now that you have done this, create a new form in the Form Designer, right click and select the Builder option. You should see a messagebox with the name of the form and the name RTMOUSE in the Called By option. Now lets extend this a bit further. When you create a builder you can invoke specific behavior by verifying how the builder was called. The items you are commonly looking for are: how the builder was invoked and what type of object was handed to the builder. The following code demonstrates how we would manipulate objects in different ways:

FUNCTION MyBuild2
LPARAMETERS poObjectReference, pcCalledBy

*-- Create variables for the object type and the calling method
LOCAL lcObjectType, lcCallingMethod

*-- Store object class type and calling information
lcObjectType = UPPER(ALLTRIM(poObjectReference.Class))
lcCallingMethod = UPPER(ALLTRIM(pcCalledBy))

*-- Process code based on object type.
DO CASE
CASE lcObjectType = "FORM"
poObjectReference.BackColor = RGB(0,0,255)
CASE lcObjectType = "TEXTBOX"
poObjectReference.BackColor = RGB(192,192,0)
ENDCASE

Now that you know how to manipulate objects on the screen lets look at adding our own objects. What you need to do now is check what type of object you are on. If it is an object that supports containership you can add other objects to it. Some containers are: forms, containers and pageframes. To do this add some code into your case structure to add an object if the object handed in supports containership. To do this you need to look at a few useful methods and functions.

Using AddObject

The first item to explore is the AddObject method. The AddObject method has two required parameters. The first is the name of the object you are about to add, the second is the class of the object you wish to add. The following code adds and displays a command on the Visual FoxPro SCREEN.

_screen.AddObject("MyButton","commandbutton")
_screen.MyButton.Visible = .t.

Whenever you use the AddObject you need to turn the Visible property on in order to see the object. This allows you to manipulate the object before showing it. Lets look at an alteration to the current builder to add buttons to a form:

FUNCTION MyBuild3
LPARAMETERS poObjectReference, pcCalledBy

*-- Create variables for the object type and the calling method
LOCAL lcObjectType, lcCallingMethod

*-- Store object class type and calling information
lcObjectType = UPPER(ALLTRIM(poObjectReference.Class))
lcCallingMethod = UPPER(ALLTRIM(pcCalledBy))

*-- Process code based on object type.
DO CASE
CASE lcObjectType = "FORM"

*-- Set a value to use to see if an object exists
lnControlLoop = 1

DO WHILE .t.
*-- Create an object name reference
lcObjectnameToTest = ["poObjectReference.cmdButton] + ;
ALLTRIM(STR(lnControlLoop,3,0)) + ["]

*-- If the object already exists then increment
*and attempt to add another one
IF TYPE(&lcObjectNameToTest.) = "O"
lnControlLoop = lnControlLoop + 1
ELSE
*-- We have a "new" object
*-- Add it
*-- Name it
*-- Turn It On
LOCAL lcNewObjectName
lcNewObjectName = "cmdButton" + ;
ALLTRIM(STR(lnControlLoop,4,0))

poObjectReference.AddObject(lcNewObjectName,"commandbutton")
poObjectReference.&lcNewObjectName..Caption = ;
lcNewObjectName
poObjectReference.&lcNewObjectName..Visible = .t.
EXIT && dont forget me. Remove for some fun
ENDIF
ENDDO

poObjectReference.BackColor = RGB(0,0,255)
CASE lcObjectType = "TEXTBOX"
poObjectReference.BackColor = RGB(0,100,0)
ENDCASE

Integrating Your Own Classes

Now you have all the components necessary to begin adding your own classes to a form. All you need to do now is open your class library using the SET CLASSLIB command and replace the class name property of the AddObject method with the name of your own class: Lets do this now:

FUNCTION MyBuild4
LPARAMETERS poObjectReference, pcCalledBy

*-- Create variables for the object type and the calling method
LOCAL lcObjectType, lcCallingMethod

*-- Store object class type and calling information
lcObjectType = UPPER(ALLTRIM(poObjectReference.Class))
lcCallingMethod = UPPER(ALLTRIM(pcCalledBy))

*-- Set to your class library
SET CLASSLIB TO MYCLASS

*-- Process code based on object type.
DO CASE
CASE lcObjectType = "FORM"

*-- Set a value to use to see if an object exists
lnControlLoop = 1

DO WHILE .t.
*-- Create an object name reference
lcObjectnameToTest = ["poObjectReference.cmdButton] + ;
ALLTRIM(STR(lnControlLoop,3,0)) + ["]

*-- If the object already exists then increment and ;
*-- attempt to add another one
IF TYPE(&lcObjectNameToTest.) = "O"
lnControlLoop = lnControlLoop + 1
ELSE
*-- We have a "new" object
*-- Add it
*-- Name it
*-- Turn It On
LOCAL lcNewObjectName
lcNewObjectName = "cmdButton" + ;
ALLTRIM(STR(lnControlLoop,4,0))
poObjectReference.AddObject(lcNewObjectName,"bcCommandbutton")
poObjectReference.&lcNewObjectName..Caption = ;
lcNewObjectName
poObjectReference.&lcNewObjectName..Visible = .t.
EXIT && dont forget me. Remove for some fun
ENDIF
ENDDO

poObjectReference.BackColor = RGB(0,0,255)
CASE lcObjectType = "TEXTBOX"
poObjectReference.BackColor = RGB(0,100,0)
ENDCASE

Adding Meta Data

The next step is to create a form that is capable of adding meta data to your forms. Meta data is sub structures of data that can be found in your tables. An example of meta data is a phone number. This is a text box with a specific format (i.e. Input mask =" (999)999-9999" ). The question is how do you accomplish the feat of finding out if a table contains meta data. Well look no further. Visual FoxPro provides you with a few functions that allow you to find out specific information about your tables. Namely CURSORGETPROP() and DBGETPROP(). For this example we are going to store some formatted text in our Field and Table comments sections of the Table Designer. We are then going to add classes to the form based on the meta data items we stored in the table. In order to do this we need to create a builder capable of opening and interigating a table for this information.

Creating The Basic Framework

The first step to creating a metadata driven builder is to read your meta data from your table. The following code represents the builder code necessary to read your meta data.

FUNCTION MyBuild6
LPARAMETERS poObjectReference, pcCalledBy

SET LIBRARY TO FOXTOOLS
SET CLASSLIB TO MYCLASS

*-- Was I called with builder lock ?
IF pcCalledBy = "RTMOUSE"
LOCAL lcTableToOpen, lcTableDatabase, laTableColumns, ;
laTableMetaData
DECLARE laTableColumns[1,11]
DECLARE laTableMetaData[1]
laTableMetaData[1] = ""

*-- Get DBF to open
lcTableToOpen = GETFILE("dbf")

IF EMPTY(lcTableToOpen)
=MESSAGEBOX("Please specify a table")
ELSE
*-- Open the table for interigation
IF USED("a_buildertable")
USE In a_buildertable
ENDIF

*-- Open the DBF
USE (lcTableToOpen) SHARED IN 0 AGAIN ALIAS a_buildertable
SELECT a_buildertable

*-- Assign database name
lcTableDatabase = CURSORGETPROP("Database","a_buildertable")
OPEN DATABASE (lcTableDatabase)

*-- Scatter fields to an array to parse through
=AFIELDS(laTableColumns)

*-- Scan through columns to find some meta data
LOCAL lnKount, lnNumberofMetaItems
lnNumberOfMetaItems = 0
FOR lnKount = 1 TO ALEN(laTableColumns,1)
IF "[[BEGINMETA]]" $ DBGETPROP(JUSTSTEM(lcTableDatabase)+ ;
"." + laTableColumns[lnKount,1],"Field","Comment")
*-- This is a single row empty table to begin with
*so use the first row first so we dont
*-- Get any empties
lnNumberOfMetaItems = lnNumberofMetaItems + 1
DECLARE laTableMetaData[lnNumberOfMetaItems]

*-- Store comment to array
laTableMetaData[lnNumberOfMetaItems] = ;
DBGETPROP(JUSTSTEM(lcTableDatabase) + "." + ;
laTableColumns[lnKount,1],"Field","Comment")

ENDIF
ENDFOR

*-- Call the builder form
DO FORM MYBUILD6 WITH poObjectReference, laTableColumns, ;
laTableMetaData

ENDIF
ENDIF

This code opens a table, opens its respective DBC(), reads the field comments and stores them to an array. It then calls a form that will present a UI for adding items to the form. The form looks like this:

Creating the Form

After creating the basic framework program you can create a UI for your developers to add controls to your form. This form takes the object reference parameter and the two arrays and attaches them to the form. Whenever a user double clicks on the Meta Data or Fields List it then adds the appropriate control to the form. There are a few custom methods on this form that you may find useful. The first is cParse; this method parses a string for a set of beginning and ending parameters. The second is cGetObjectName; this method create a new name for the object you are about to add. The source code files for these methods and this form are included on the session disk.

Conclusion

In this session you learned some of the basics of creating and integrating your own object oriented classes into automation tools like builders. This is just the tip of the iceberg. For the ambitious in the crowd you could:

As you can see creating builders can be both fun and increase your productivity. When you return to your office feel to use the code included on the disk and adapt it to your own needs.