Session E-WIZZ
The Builder Builder's Reference
Steven M. Black
SBC / UP!
Introduction
Great news from VFP comes from how the Class Designer (cd) and the Form Designer (fd) interact with the development environment. All Visual FoxPro objects are endowed with both design-time and run-time manifestations and public interfaces.
Custom programs, written in FoxPro, can manipulate our design-time work. For the first time support for this sort of thing is built into the language. And it's extensible, because the architecture is open. This has really profound implications.
Here is the definition of "Builders" as found in the Visual FoxPro Help file:
Visual FoxPro tools, such as the ListBox Builder, that help the user set properties for a specific control, or combine clauses to create a specific expression. A Builder consists of one or more dialog boxes that present a simple set of choices.
That seems rather limited.
Here is the definition of "Wizards", taken from the same source:
A sequence of dialog boxes that present specific questions about what you want to create. Once you answer the questions, the Wizard completes the task.
These definitions suggest that Builders and Wizards are friendly interactive things, dialogs really, that set properties and generally get things started or done. More importantly, the simple task-oriented tone of that definition is good for marketing folks who can easily understand and demonstrate such things.
Here is a wider, more developer-centric definition:
A Builder is any tool that helps us construct software. This includes native and custom Wizards, native and custom Builders, and any process that automates building and maintaining software.
This wider definition recognizes that, sure, Builders and Wizards can be cute dialog-based programs that set properties. But they also can add, audit, backup, build, compile, delete, document, edit, find, harmonize, localize, maintain, merge, reference, restore, substitute, transform, update, validate, write... whatever. Invisibly or otherwise. Builders open a door a big door, a door so wide you could drive a Mack truck through it! There's little in Visual FoxPro that cannot be done with a custom Builder.
This document serves as an evolving reference for creating Builders and Wizards. Everywhere herein "Builders" are mentioned also applies to Wizards because, in my understanding, Wizards are a form of Builder.
This cannot possibly pretend to be a complete treatment of Builders and Wizards. Builders, as I have defined them, are a wide and diverse subject. In the coming years we will see some remarkable software engineering, some of it occurring through the application of Builders. My goal right now is to make developing Builders and Wizards easier.
This Builder and Wizard Reference
If you think about it, we've had Builders for a long time. FoxPro 2.6 was the first version to ship with Wizards. The Flash Development Toolkit, to take another example, is chock full of individual Builders for FoxPro 2.x (and which, for the most part, work unchanged in VFP). FoxExpress is a cool Wizard if ever there was. GENSCRNX and its variants and add-ins (like 3D.PRG) are all Builders, though we never called them that before.
This document is organized into an alphabetic presentation of helpful things to consider when building your own Builders and Wizards in Visual FoxPro. At first glance the sequencing doesn't seem very imaginative. Hopefully you will feel differently when you need this more for reference and not intending to make this a sequential read.
From here forward, unless otherwise specified, "Builders" means "Builders and Wizards".
_BUILDER
Like _GENMENU, _GENGRAPH, _BROWSER and the others, _BUILDER is a system memory variable used to point to a Builder application. The value of _BUILDER defaults to HOME()+"WIZARDS\BUILDER.APP". You can set the default _BUILDER in the File Locations page of the Tools-Options dialog or in CONFIG.FPW.
If you use Windows NT, this and all other kindred configuration information is kept in the NT registry under HKEY_CURRENT_USER|Software|Microsoft|VisualFoxPro|3.0|Options. The registry entry will be overridden by any valid _BUILDER setting in CONFIG.FPW, and as always, you can interactively change the _BUILDER system memory variable from the command window. See also the BUILDER.APP, Native Builders, and _WIZARD topics below.
You may, as always, substitute your own Builder manager for the native BUILDER.APP. We may soon see a BUILDERX in the near future. Stay tuned, and remember that word of great Builders will likely appear in CompuServe's FoxUser forum months before you'll read of them in magazines.
_WIZARD
Like _BUILDER, this is a system memory variable used to point to a Wizard application. The value of _BUILDER defaults to HOME()+"WIZARDS\WIZARD.APP". Like for _BUILDER, you may assign any program to act as your Wizard application.
Access denied
Everything you can do interactively with the Command Window is doable with Builders. Here are broad categories of things you can't modify with FoxPro code.
Class Hierarchy Properties, which are: Baseclass, Class, Classlibrary, OLEclass, and Parentclass. To change an object's class you need to either substitute a new object having the correct pedigree, or alternatively close the fd and open and change the underlying SCX. There is no way to reconfigure class pointers of in-situ objects because they are fullblown live objects, even when seemingly just displayed by the design tools.
Control Array Assignments like the Buttons, Columns, Controls, Forms, and Pages properties cannot be modified at design-time. The control array index sequence is a function of the Z-order, and the length of the control array is determined by the number of instantiated objects.
Object Count Properties, like Controlcount, Formcount, and Listcount, are a read-only and determined by the number of objects present.
The Program Status Properties include Activecolumn, Activeform, Activerow, Activecontrol, Docked, Dockposition, Formindex, Itemdata, Itemiddata, List, Listindex, Listitem, Listitemid, Relativecolumn, Relativerow, Selected, Sellength, Selstart, Seltext, Topindex, and Topitemid. These properties have no tangible meaning at design-time and cannot be set by Builders.
Adding objects
Builders can add objects to containers. For example, executing the following code will add a Label named lblHello and a Commandbutton named cmdQuit to a Form in the fd session:
*-- You may execute this Builder from the
*-- command window without touching _BUILDER
*-- or BUILDER.DBF.
*-- Start a form designer session.
MODI FORM Junk NOWAIT
*-- Find the parent container, in this case it's the Form.
=ASELOBJ( laObjectArray, 1)
*-- Add a Label named lblHello
laObjectArray[1].AddObject( "lblHello", "Label")
*-- Add a Commandbutton named cmdQuit
laObjectArray[1].AddObject( "cmdQuit", "Commandbutton")
*-- Set some properties.
WITH laObjectArray[1].lblHello
.Caption= "Hello"
.Top= 10
ENDWITH
WITH laObjectArray[1].cmdQuit
.Caption= "Quit"
.Top= 100
ENDWITH
AMEMBERS( ArrayName, ObjectName [, 1 | 2])
AMEMBERS() is useful for determining all sorts of information about objects and what objects and members they contain. Keep in mind that it's possible to make properties and methods PROTECTED, which hides them. A Builder cannot affect protected properties and methods, so you should check for the visibility of properties and methods before attempting to modify them.
Description: Creates or updates an array of properties, procedures, and member object names, and returns the number of rows in the array.
Parameters:
ArrayName: The array into which the names of the member properties for ObjectName are placed.
ObjectName: Specifies the object whose member properties are put in array ArrayName.
1: Specifies that the resulting 2-D array contains properties, events, methods, and member objects.
2: Specifies that the resulting 1-D array contains only the member object names.
For an applied example using AMEMBERS(), see Drilling down below.
Array members
A weakness in the fd and cd interfaces is that array member properties cannot be changed in the Properties Sheet. Builders, on the other hand, may change the contents of array member properties, but cannot change the DIMENSION of member arrays.
One way to change the DIMENSION of a member array property is to remove and recreate the array property. The other is to edit the SCX or VCX directly. See the last part of the Reset to Default (properties) and Custom members topics for information on how to do this.
ASELOBJ(ArrayName, [1 | 2])
This gets my vote as one of the best new functions in Visual FoxPro. It is wonderfully functional, and sparse as it is, I can think of no enhancements to suggest for it.
Description: Creates or updates an array of object references to currently selected controls in the active fd, and returns the number of rows in the array.
Parameters:
ArrayName: The array into which the object references are placed.
1: Creates a 1-D array containing an object reference to the container for the currently selected control.
2: Creates a one-item array containing an object reference to the DataEnvironment object for the form.
ASELOBJ() is perhaps the most useful function when using Builders. Here is a table of ASELOBJ() examples:
Task | Code | Comments |
---|---|---|
To reference currently selected object | =ASELOBJ( MyArray) | |
To reference all currently selected objects | =ASELOBJ( MyArray) | Contents in Z-order Sequence |
To reference object's container | =ASELOBJ( MyArray,1) | |
To reference the form | =ASELOBJ(
MyArray,1) DO WHILE MyArray[1].BASECLASS<>"Form" MyArray[1]=MyArray[1].Parent ENDDO |
|
To reference the Formset | =ASELOBJ(
MyArray,1) DO WHILE MyArray[1].BASECLASS<>"Formset" MyArray[1]=MyArray[1].Parent ENDDO |
|
To reference the DataEnvironment | =ASELOBJ( MyArray,2) |
Builder property
If you add a Builder property to your class, BUILDER.APP will recognize this, and invoke the builder specified there. This means that you can assign a specific Builder to work with the objects you create from classes.
If the Builder program specified by the object's Builder property is missing or invalid, BUILDER.APP generates a friendly warning, then the usual BUILDER.APP program executes, which is as graceful a recovery as one could expect.
A disadvantage of using a Builder property: BUILDER.APP executes this Builder without ever presenting a picklist of other registered Builders that may apply. The Builder property is interpreted by BUILDER.APP as an exclusive Builder, which is regrettable. See BUILDER.APP and BUILDER.DBF below.
BUILDER.APP
The BUILDER.APP that comes with FoxPro is not a Builder, but rather a Builder manager. Its function is to take stock of the situation, invoke (or present a choice of) the appropriate Builder program, and return you cleanly to the FoxPro design environment. BUILDER.APP is a modal application. The source code for BUILDER.APP and all the native Builders is not included in VFP.
After working for several weeks with Builders, I find myself working around it more than with it.
BUILDER.DBF
The Builder registration table, BUILDER.DBF, lives in HOME()+"WIZARDS" directory. VFP's standard BUILDER.APP checks this table for Builders of a particular type. If more than one record of the same type is present, a picklist is presented to you. Otherwise the program specified in the Program field is invoked without fanfare.
BUILDER.APP does not provide services for registering and unregistering Builders in BUILDER.DBF. You must maintain registration records interactively. If BUILDER.DBF is missing, BUILDER.APP will recreate the standard BUILDER.DBF.
Tip: Using the Form Wizard, create a BUILDER.DBF viewer, then register it as a Type="ALL" builder. You can then use any convenient Builder hook to manage the behavior of BUILDER.APP. For more details, see Registering Builders and WIZARD.APP.
The structure of BUILDER.DBF is exactly that of WIZARD.DBF.
Field Name | Type | Description |
---|---|---|
NAME | C-45 | Name of the Builder. This appears in the picklist displayed by the standard BUILDER.APP when there is more than one Builder for this class of object. |
DESCRIPT | M-4 | Long description for the Builder. This is also displayed by the standard BUILDER.APP when there is more than one Builder for this class of object. |
BITMAP | M-4 | Not currently used by builders. |
TYPE | C-20 | Contains a
keyword, such as a FoxPro Baseclass name,
"ALL", or some other moniker that identifies
the type of Builder or the context in which it is to be
called. Note: The algorithm used by BUILDER.APP to determine the Type is not public, not data-driven, and cannot be changed. This, above all else, makes BUILDER.APP difficult to work with. |
PROGRAM | M-4 | The program to invoke. |
CLASSLIB | M-4 | The class library that contains the Builder (if in a VCX). |
CLASSNAME | M-4 | name of the class of the Builder. |
PARMS | M-4 | Parameters to pass to the PROGRAM. |
Builders Vs Wizards
Visual FoxPro comes with many Wizards and Builders. Most other Microsoft products come with only Wizards. Why the difference? It's due mainly to a change in parlance at Microsoft. A rule to distinguish between them: If it gets you started it's a Wizard, otherwise it's a Builder.
In my parlance, a Wizard is a specific type of Builder. So my rule is different: Assume something is a Builder, and if it gets you started on something, then it is also a Wizard.
Builders and Wizards are very similar. For example, the Registration tables (BUILDER.DBF and WIZARD.DBF) are identical in structure, though some fields are used by Wizards but not by the native Builders. As you would hope, the native Builders and Wizards are written in FoxPro, and they reportedly share some code, and they share some elements of WIZSTYLE.VCX.
CloneObject(NewName)
A design-time method, applicable to most types of controls, that creates an exact duplicate of an object in its current container. Useful for, among other things, creating backups before your Builders run amok. See the Undo topic below.
The clone is such that all properties (including Top, Left, Width...) are identical. Therefore you can't tell you created a clone just by looking at it; you need to move the clone in order to see both the clone and the original.
One would think, given VFP's automatic object naming abilities, that the NewName parameter would be optional. It isn't.
Control Classes
Control classes hide all the details of their members unless you are working in the cd. So Builders launched from the fd cannot penetrate Control classes.
Custom members
When working in the fd, only Form objects can accept custom properties and methods. When in the cd, only the "background" object can accept custom properties and methods. To convince yourself of this, open a CommandGroup in the cd, add try to add a custom property to one of the CommandButtons. It can't be done.
In any event, how to add properties and methods to Forms in the fd or to classes in the cd? In Visual FoxPro 3.0, KEYBOARD stuffing using the Form or Class menu seems to be the only way. The following function can be used by Builders to programmatically add custom members.
********************************************
FUNCTION AddMemb
* Purpose: Invoked by Builders to add properties
* or methods to objects.
* Syntax: AddMemb( cMemberName [,cDescription[, lIsMethod]])
*
PARAMETERS tcProperty, tcDescription, tlMethod
LOCAL lcMenuKey, lcKeyboard, ARRAY laObj[1]
*-- Test and sanitize the parameters
IF EMPTY( tcProperty) OR TYPE( "tcProperty") <> "C"
RETURN .F.
ENDIF
IF EMPTY( tcDescription)
tcDescription= ''
ENDIF
*-- Bail if the form designer isn't up
lcMenuKey= ''
DO CASE
CASE "FORM DESIGNER" $ UPPER( WONTOP())
lcMenuKey= 'M'
CASE "CLASS DESIGNER" $ UPPER( WONTOP())
lcMenuKey= 'C'
OTHERWISE
RETURN .F.
ENDCASE
*-- Bail if the property exists already
=ASELOBJ( laObj, 1)
IF TYPE( "laObj[1].&tcProperty") <> "U"
RETURN .F.
ENDIF
ACTIVATE WINDOW (WONTOP())
lcKeyboard= "{Alt+" + lcMenuKey + "}" + ;
IIF( tlMethod, "M", "P") + ;
tcProperty+ ;
"{TAB}" + ;
tcDescription+ ;
"{Ctrl+W}"
KEYBOARD lcKeyboard
RETURN .T.
Dataenvironment manipulation
You can create a Dataenvironment reference (and influence the Cursor and Relation objects therein) by using 2 as the second parameter in ASELOBJ(). For example:
*-- Start the Form Designer. This will do.
MODIFY FORM JUNK NOWAIT
*-- Store the Dataenvironment reference in MyArray[1]
=ASELOBJ( MyArray, 2)
?MyArray[1].Baseclass && "Dataenvironment"
Drilling down
Drill-down Builders act on an object and, if the object is a container, then acts on the objects that may be contained therein. Implication: forget the notion that Builders should work only on selected objects.
Visual FoxPro comes with a handy SetAll() function for setting properties (see the SetAll topic below), but there are times when you need better control than SetAll() can give.
Drill-downs are useful when we need to affect everything, at whatever the level, in a container. For example, a Builder to change the Fontsize on all objects in a Form could ignore the selected objects and work directly with the Form and all its contents. A Builder that enforces standards and guidelines might be expected to audit an entire Form, and not be limited to what controls happen to be selected.
There are a variety of ways of structuring a drill-down, all employing one or more of the following steps:
Find the parentmost container (if required), and
process the container (if required), then
process every object in the container, and
repeat steps 2 and 3 as required.
For example, here is a trick-or-treat Builder that makes everything in a container a random color. Note the variant: if an object is not selected (perhaps the Builder is invoked from the command window) then the Builder acts on everything. See also Pageframes and pages.
**********************************************************
* Colormix.PRG
* Demonstrates a Drill-down Builder.
*
* Danger: This will randomly change colors in
* the form designer.
*
* Also an example of using recursion in Builders.
*
* For 10 points: Why is achieving the effect of this
* program impossible with SetAll()?
*
PARAMETERS oSource, cOther
LOCAL i
LOCAL ARRAY laSelObj[1]
* /////////////////////////////////////////////
* Your recursed Builder code goes here in this block.
*-- If there's a Forecolor property change it randomly.
IF TYPE("oSource.ForeColor") <> "U"
oSource.ForeColor= RAND() * 256^3-1
ENDIF
*-- If there's a Backcolor property...
IF TYPE("oSource.BackColor") <> "U"
oSource.BackColor= RAND() * 256^3-1
ENDIF
* End of your code
* \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
* Are we in a container and, more to the point,
* are there any objects inside?
IF ( AMEMBERS( laSelObj, oSource, 2) > 0)
FOR i= 1 TO ALEN( laSelObj)
lcObject=laSelObj[ I]
*-- Recursion hook.
=colormix( oSource.&lcObject)
ENDFOR
ENDIF
RETURN
Errors
The error handling you need still depends on your tolerance for pain, but Builders present a special challenge. We are all accustomed to treading carefully with data. Now we must do the same with source structures, because that's what Builder programs modify! GENSCRNX users have often found comfort in knowing that all changes are made on temporary structures. This isn't true in VFP with BUILDER.APP.
At this time I don't have specific recommendations to make with regard to errors and Builders, other than to point out that, as usual, a clumsy error without a backup can ruin your day.
Grouping
In this release of Visual FoxPro, there is no way to group screen objects as we would in FoxPro 2.6. Fortunately, ASELOBJ() can distinguish if multiple objects are selected, and will create an array of object references, one for each selected object. So unlike the Properties Sheet, Builders can act on multiple objects.
Invoking Builders
There are seven or more ways to invoke Builders, five of which are standard hooks from within FoxPro.
I won't dwell on the first five items in this list. These are sufficiently documented in Help, the VFP manuals, and already in many articles.
Invoking Builders from custom toolbars is an intuitive approach to managing builders. Mechanisms for calling your various builders from within click events of toolbar controls is easy to set up. A nice variant: create a _SCREEN.Timer as a voyeur class that reacts to the activation and deactivation of the fd by showing and hiding the appropriate toolbars, including your builder toolbar.
Browser drag and drop builders are created by placing Builder code in the Init of sacrificial builder classes stored in VCX or SCX structures, with the Init ultimately returning .F. so no object is created. Presto: To run a builder, drag its class from Browser to the desktop. The Init code fires (which runs the builder) and returns false. A variant: Several builders can be combined into a container. The Builder elements execute in Z-order.
Invoking Wizards
You may invoke a Wizard programmatically with
DO (_WIZARD)
This brings forth a selection dialog containing all the Wizards registered in WIZARD.DBF. You can bypass this dialog
DO (_WIZARD) WITH 'FORM'
In this case, since more than one Wizard exists for this situation VFP ships with both a Form and One-to-Many Form Wizard the selection dialog is presented, and its list contains just the two Form Wizards. If only one Wizard of Type="FORM" existed, there's no selection dialog, and the wizard would be launched directly.
To explicitly run a given Wizard, specify the Wizard name or class in the second parameter.
DO (_WIZARD) WITH 'FORM', 'One-to-Many Form Wizard'
The mechanism with the second parameter is as follows: By default the Program field in WIZARD.DBF is searched for the second parameter. If nothing is found, then the Classname field is searched.
WIZARD.APP passes through up to seven additional parameters to the selected wizard program.
DO (_WIZARD) WITH SomeType, WizardName, Param3,... Param9)
Native Builders
Visual FoxPro lists the following Builders in BUILDER.DBF.
Builder | Description |
---|---|
Edit Box Builder | Creates and sets properties for an edit box control. |
Option Group Builder | Creates and sets properties for buttons in an option group. |
List Box Builder | Creates and sets properties for a list box control. |
Grid Builder | Creates and sets properties for a grid control. |
Form Builder | Adds controls to a form using fields and a style the user specifies. |
Combo Box Builder | Creates and sets properties for a combo box control. |
Command Group Builder | Creates and sets properties for buttons in a command group. |
AutoFormat Builder | Applies a style to selected controls on a form. |
Referential Integrity Builder | Creates triggers and stored procedures that enforce referential integrity between tables in your database. |
Text Box Builder | Creates and sets properties for a text box control. |
AutoFormat Builder | Applies a style to selected controls on a form. |
Native Wizards
Visual FoxPro lists the following Builders in WIZARD.DBF. (v ) indicates an improved Wizard over the 2.6 version. (Õ ) indicates that this Wizard is included in the Professional Version only
Wizard | Description |
---|---|
Form v | Creates a form based on a single table |
One-To-Many Form | Creates a 1-Many form with a grid control for child table |
Report v | Creates a report based on a single table |
Group / Total Report v | Creates a report with groupings and totals |
One-To-Many Report v | Creates a 1-Many report |
Label v | Creates a mailing label report based on pre-defined styles |
Import v | Imports a foreign file format to a FoxPro table |
Documenting Õ v | Documents and formats FoxPro source files |
Table v | Creates a new table |
Local View | Creates a view using local data |
Remote View | Creates a view using remote data |
Query v | Creates a standard query |
Graph v | Creates a new graph using MS Graph |
Cross Tab v | Creates a new cross tab table based on existing data |
Pivot Table | Creates an Excel PivotTable based on FoxPro data |
Mail Merge v | Creates a Word Mail Merge document based on FoxPro data |
Upsizing Õ | Moves FoxPro data files to SQL Server files |
Setup Õ | Creates installation program for distribution of custom apps |
Modal vs modeless Builders and Wizards
Builder programs can be modal or modeless. The native BUILDER.APP and WIZARD.APP are modal, but that's not to say that your custom Builders and Wizards should be. Modeless programs are more susceptible to unforeseen problems brought on by external factors. This can be reason enough to eschew modelessness. But needlessly modal programs can be a pain to use. You must decide this issue based on what factors balance best for you.
Multiple selections
Builders can act on multiple objects as selected in the Form or Class Designer. ASELOBJ( MyArray) can be used to populate MyArray with object references. The sequence of pre-selection is irrelevant, and the array is sequenced in object Z-order.
Native builders include an AutoFormat Builder, which you will see if you invoke FoxPro's standard BUILDER.APP while many objects are selected.
Pageframes and Grids
After creating a few Builders, some common 'workaround' themes emerge. One of these themes results from the incongruous nature of Grids and Pageframes compared to other sorts of containers. Whereas all other containers can contain controls, Grids can only contain Columns, and Pageframes can only contain pages.
This means that Pageframes and Grids do not have Controlcount and Controls[i] properties. Drill-down routines cannot therefore rely on the existence of Controlcount property to determine if a given object is a container. Similarly, you cannot assume that members of container objects possess Top and Left properties, or can even GetFocus().
This explains why the drill-down routine in the Drilling down topic uses the slower but consistent AMEMBERS(,,2) to determine if a given object is a container and, if so, what it contains.
Parameters passed to _BUILDER by VFP
VFP passes two parameters to the program designated by _BUILDER. The first parameter is a 1-D array of selected object references (see the ASELOBJ() topic), and the second parameter varies according to how the Builder was invoked, as in the following table:
Invocation | Second Parameter passed by standard BUILDER.APP |
---|---|
Builder Lock on the Form Controls toolbar | "TOOLBOX" |
Property Sheet button | "PSHEET" |
RIGHTMOUSE pop-up menu. | "RTMOUSE" |
Form Design Toolbar | "QFORM" |
Manual invocation | Whatever you pass upon invocation |
Parameters passed to Builders by BUILDER.APP
If _BUILDER is set to BUILDER.APP, then BUILDER.APP passes three character type parameters to registered Builders.
The first parameter is the string "wbReturnValue", which is puzzling unless you know the following: Sometimes BUILDER.APP or WIZARD.APP need to return a value to the calling routine. Fine, except that someone decided that Builders and Wizards should not be called as procedures or functions (something I do all the time!). "WbReturnValue" is the name of a memvar created by BUILDER.APP and WIZARD.APP that should be updated with the return value. In practice you'll probably never need to know this fact.
"Para1, Para2, Para3..." or, more precisely, whatever is contained in the PARMS field of WIZARD.DBF or BUILDER.DBF. You may use this string as you please. Most basic custom Builders called by BUILDER.APP will really only use this parameter.
M.wbcpOptions, the third parameter, contains the keyword information that may have been passed into BUILDER.APP (see the table in the previous section). Also added to this string are any other parameters that were received in the placeholder parameter slots, separated by spaces.
It may be helpful to know that so far I've never needed to use the parameters passed to builders. ASELOBJ() can typically tell us everything we need to know.
Parameters passed to _WIZARD by VFP
In most cases, VFP passes a single parameter to _WIZARD, a string to look-up in the TYPE field of WIZARD.DBF. If the _WIZARD is invoked from the Project Manager, then three parameters are passed: 1) A string to look-up in the TYPE field of WIZARD.DBF, 2) a string of length zero, and 3) the string "NOTASK".
Parameters passed to Wizards by WIZARD.APP
Visual FoxPro's native WIZARD.APP passes nine parameters to its Wizards.
Parameter1: Reference to a variable containing the name if the Wizard generated file. This memory variable gets passed back to VFP when the Wizard is finished. This is so other resources, the Project Manager for example, can be refreshed upon return from a Wizard launched there.
Parameter2: This parameter is the contents of the Parms field in the Wizard registration table.
Parameter3 - Parameter9: These are extra parameters passed to _WIZARD. They are optional parameters which are not used by the standard shipping Wizards, but can be used by your custom Wizards.
Implications for Wizard writers:
Your Wizards must provide a 9-element PARAMETER statement to handle the call from WIZARD.APP.
The first parameter is useful only if you want to have the Wizard output reflected in the Project Manager, and if the Wizard emanates from the Wizard button on the new file dialog in the Project Manager.
Registering Builders
The native BUILDER.APP program that ships with VFP relies on the BUILDER.DBF table that lives in the VFP Wizards directory. To register a Builder is to manually add a record in this table. The native BUILDER.APP offers no services to transact with BUILDER.DBF. See the BUILDER.DBF topic.
Here is an easy way to give a registration interface to BUILDER.APP:
* BLDRUTIL.PRG
PARAMETERS X, Y, Z
DO FORM BUILDER.SCX
Field | Entry |
---|---|
Name | BUILDER.DBF maintenance |
Descript | Edit BUILDER.DBF |
Type | All |
Program | BldrUtil.PRG |
Now you can converse with BUILDER.DBF just by launching BUILDER.APP since the "All" builder type is, as you might expect, universal.
Relational Integrity (RI) Builder...and future wrappers
Unconvinced of the power of Builders? Then note that Relational Integrity in Visual FoxPro is enforced by code written by a Builder. Not too shabby for power, I'd say. To convince yourself of all this, invoke and run the RI Builder from the Database Designer (DD) with a right mouse click. Then look at the Stored Procedures, also available through a right mouse click on the DD. All that delineated code was written by the RI builder (See the WRITEEXPRESSION() and WRITEMETHOD() topics below).
This means that, among other things, if you don't like FoxPro's implementation of Referential Integrity, then you can wrapper the current RI builder, or even create your own.
A helpful hint if you wrapper any builder: When multiple copies of a function are found in the same FoxPro source file, the last one always gets executed. This means that you can append code to the RI code in the Stored Procedures, and your versions of duplicated functions will execute.
ReadExpression( cPropertyName)
Description: Design-time method that returns the expression stored to a property. To write expressions to properties, use WriteExpression().
Parameters:
cPropertyName: Specifies the name of the property to return the expression from.
Example:
CREATE FORM SYS(3) NOWAIT
=ASELOBJ( aForm, 1)
oForm=aForm[1]
oForm.Caption= "Form Title"
? oForm.Caption && = "Form Title"
? oForm.ReadExpression("Caption") && = nothing -- Take Note!
oForm.Caption= CDOW( DATE())
? oForm.Caption && = "Tuesday"
? oForm.ReadExpression("Caption") && = nothing -- Take Note!!
oForm.WriteExpression("Caption", "=CDOW( DATE())")
? oForm.Caption && = "Tuesday" -- Take Note!
? oForm.ReadExpression("Caption") && = "=CDOW(DATE()+1)"
As shown, it is possible to store an expression to a property and have ReadExpression() return blank. This is not a bug. When we say oForm[1].Caption= CDOW(DATE()), we are storing the value of CDOW(DATE()) to the Caption property. To store an expression to a property, use WriteExpression().
ReadMethod( cMethod)
Description: Design-time method that returns method code as text. Use ReadMethod() to reckon the code (if any) in an object code snippet.
Parameters:
cMethod: Specifies the name of a method.
All the code in the specified method is returned as a string with embedded character returns, tabs, comments, and whatever else is in your code.
Reset to Default (events and methods)
When the cursor is on the properties section of the Properties Sheet, RIGHTMOUSE invokes a popup menu that contains, among other things, a "Reset to Default" pad that restores the Parentclass's properties, events, and methods.
To reset events and methods to default, use WRITEMETHOD() to set the snippet to an empty string. For example, to reset the INIT of the currently selected object:
*-- Get a handle on the currently selected object
=ASELOBJ(x)
*-- Here set the Init event to the Parentclass default.
X[1].WriteMethod( "Init", "")
Reset to Default (properties)
Resetting properties to default cannot be done directly there is no command or straightforward mechanism to do this.
One indirect way is to instantiate a dummy object and poll its property values. Doing this doesn't really "Reset to Default" since you won't be inheriting; the default value is hard-coded and happens to be, for now, equal to that of the default.
If you are intrepid, a Builder can always release the SCX, USE the SCX, navigate to the correct record, delete the appropriate lines in the Properties memo, close the SCX, issue a COMPILE FORM ..., then re-open the SCX again. It isn't pretty for one thing there is currently no way to 'reselect' the originally selected objects but it otherwise works.
SaveAs( cFileName [, oObjectName])
Description: Design-time or runtime function that saves a .VCX-based Form or Formset as an .SCX.
Parameters:
cFileName: The SCX file to create.
oObjectName: Specifies a reference to a Dataenvironment object. Specify oObjectName when you want the Dataenvironment saved with the form.
Example uses: You could use the SaveAs method to store all the PEMs associated with an object to another .SCX file. Also you can CREATEOBJECT() an object or even a whole form, customize it from the command window, and use .SaveAs() to programmatically save it for future use. See the Undo topic below for another example.
SaveAsClass( ClassLibName, ClassName [,Description])
Description: Design-time or run-time method that saves any .VCX/SCX based class (except Dataenvironment, Page, Column, and Header) in a class library.
Parameters:
ClassLibName: The name of the class library file name.
ClassName: The class name.
Description: An optional description for the class..
Here's an interesting thing about SaveAsClass: as long as the class is VCX-based, then programmatic class transformations can happen and SaveAsClass() will work downstream. This is useful for importing classes developed in code into a VCX. Example:
*-- Create a VCX-Based form class called MyForm
Foo= CREATEOBJECT("Form")
Foo.SaveAsClass("MyClasses","MyForm")
*-- MyForm is now VCX-Based
SET CLASSLIB TO MyClasses
*-- a code grandchild of MyForm
Foo= CREATEOBJECT("MyExpandDialogForm")
*-- Create a VCX of the grandchild.
Foo.SaveAsClass("MyClasses","MyExpandedDialogForm")
*-- SUPPORTING CLASSES
*-- code transformation, creating a child code class
*-- from MyForm VCX class
DEFINE CLASS MyDialogForm as MyForm
Width = 200
Height = 200
Caption = "Dialog Form"
BackColor= RGB(192, 192, 192)
ENDDEFINE
*-- child transformation, creating a code grandchild from
*-- child code class.
DEFINE CLASS MyExpandDialogForm as MyDialogform
Width = 400
ENDDEFINE
SetAll( cProperty, Value [, cClass])
Description: Assigns a property setting on all, or a certain class of, controls in a Container object.
Parameters
cProperty: The property to set.
Value: The new setting for the property.
cClass: Specifies the class name, that is the class on which the object is based, not the Visual FoxPro base class for the object.
Example: make all the current form's Pageframe Pages green.
_SCREEN.ActiveForm.SetAll('ForeColor', RGB(0,255,0), 'Page')
Very useful and powerful! See also the Drilling down topic.
A bummer: If you issue a SetAll() for a user-defined (i.e. a non baseclass) class that isn't instantiated or doesn't otherwise exist, VFP generates error 1733, "Class definition ClassName not found".
Show me
Builders are an excellent way to learn the minutia of Visual FoxPro controls. Puzzled about how to create a Grid with a given look or behavior? Use the Grid Builder to construct something similar, then reverse-engineer it.
Subclassing
In the end, you will be better off with a sensible class hierarchy and reusing your designs rather than relying on Builders and Wizards to repeatedly create application objects. Here's why.
If you extensively use the native VFP Wizards and Builders to create an application, then eventually you will hit a wall of maintenance difficulties. This because native Wizards and Builders use classes from WIZSTYLE.VCX, which contain some questionable points of design. Most notably:
Most WIZSTYLE.VCX composite classes are built directly from VFP base classes. Questionable design! An application populated with these objects will be less maintainable because the properties, events, and methods of base classes are read-only. To make global changes to your WIZSTYLE.VCX-based application, you need to visit each appropriate object instance, just like you do in FoxPro 2.6. Ironically WIZSTYLE.VCX contains several controls spawned from base classes (with names like StandardLabel and EmbossedLabel, for example) but the composite classes in WIZSTYLE.VCX don't use them.
Most classes in WIZSTYLE.VCX (15 of 39, at last count) are containers wherein live Labels, Shapes, and other controls that comprise the bulk of the WIZSTYLE.VCX records. That's convenient for Wizards, which need to tailor the Labels and Shapes in concert with the controls and what's easier than addressing them through a common container? But working with these objects over the long term, what's more irritating than to forever pierce through otherwise spurious containers to reach or address objects? Bleh!
You might suppose a great notion is to manually edit WIZSTYLE.VCX so the classes within composites have a better class pedigree. (This is remarkably easy to do. Just change the Class and perhaps the ClassLoc fields.) CAUTION!! This class library is referenced by native Builders, native Wizards, the sample applications, and many code samples you will stumble upon in the future. Change things at your peril, because you won't have access to the source code of some of these things.
In the end, it appears that a very shallow, or more to the point, faster class hierarchy was preferred by the VFP design team for Builders and Wizards. On balance, that's understandable.
Undo
Builders will usually make changes directly to meta data source tables. A well designed Builder will let its users abort and undo Builder processes. This means you need good state saving and restoring mechanisms. A reasonably good way to do this is use the Form.SaveAs method to make a physical backup of the screen before you start. Also consider that you can make archive copies as you go along, so allowing users to go backward one step is easily doable.
Tip: the CloneObject method may be invoked by Builders to create backup copies of individual objects. Two problems with CloneObject: Forms don't have a CloneObject method, and CloneObject makes a physical copy of the object in the current form, so you must remember to delete the backup when you're done.
Here is a skeleton for a Builder that saves the Form using the SaveAs method before doing its deed.
* SaveExample.PRG
* Skeleton Builder demonstrating
* state-saving
PARAMETERS toPassedObject, tcContextString
LOCAL loForm, lcArchive
*-- Locate the form object
loForm= toPassedObject
DO WHILE loForm.Baseclass <> "Form"
loForm= loForm.parent
ENDDO
*-- make an archive
lcArchive= SYS(3)
loForm.SaveAs( lcArchive)
* <<< Rest of Builder goes here >>>
Where's BUILDER.VCX?
If you use FoxPro's native Builders, sooner or later you will wonder where they get their class information. Perhaps you want to customize these classes for your particular needs. The Classlib field of BUILDER.DBF contains many references to BUILDER.VCX, so there must be a BUILDER.VCX resource for BUILDER.APP, right? Not that you can use! BUILDER.VCX is bound inside BUILDER.APP, so you can't get to it.
This belies the wider difference between BUILDER.APP and WIZARD.APP. Operationally, all the Builders are in BUILDER.VCX (which is in BUILDER.APP), but the Wizards are in separate apps. If you browse WIZARD.DBF you will see that the Program field specifies an APP for each Wizard.
You can still replace Builders even though they are built in. If you add another grid Builder for example, you will be prompted to select what Builder you want (i.e. there will be 2 entries in BUILDER.DBF that have type of GRID). Similarly, you can delete the registration for a native builder, but cannot actually delete it since it is bound in BUILDER.APP.
WIZARD.APP
Like the native BUILDER.APP, WIZARD.APP isn't a Wizard, but rather a Wizard manager. Its role is to invoke the appropriate Wizard according to the implicit or explicit conditions of its invocation.
See Also Invoking Wizards above.
WIZARD.DBF
The Wizard registration table, WIZARD.DBF, lives in HOME()+"WIZARDS" directory. The structure of WIZARD.DBF is as follows
Field Name | Type | Description |
---|---|---|
NAME | C-45 | The name of the Wizard as shown in the Wizard selection dialog. |
DESCRIPT | M-4 | The description of the Wizard as shown in the Wizard selection dialog. |
BITMAP | M-4 | The name and path of the bitmap which appears in the Wizard selection dialog |
TYPE | C-20 | The wizard type |
PROGRAM | M-4 | The program file called to invoke a specific Wizard. |
CLASSLIB | M-4 | The VCX file containing the Wizard if a program is not provided in the Program field |
CLASSNAME | M-4 | The class name of the Wizard if a program is not provided in the Program field. |
PARMS | M-4 | Parameter(s) passed to Wizard APP file if needed. |
WriteExpression( cPropertyName, =cExpression)
Description: Design-time only function to write expressions to properties.
Parameters:
cPropertyName: Specifies the name of the property to write the expression to.
cExpression: Specifies a character string that is written to the property sheet as an expression.
This function is not as straightforward as is seems because of the special way expressions are stored. VFP expects the expression expressed as a stream of characters and preceded by "=". For example, to place the current date expression in a Commandbutton, do as follows:
*-- WRITEEXPRESSION() Example
*-- Places a date expression inside a Commandbutton
*-- Start the form designer. This will do.
MODI FORM Junk91 NOWAIT
*-- Find the parent container of the selected object
=ASELOBJ( laObjectArray, 1)
*-- Add a Commandbutton
laObjectArray[1].ADDOBJECT( "Command1", "CommandButton")
*-- Take Note! VFP expects an "=".
laObjectArray[1].Command1.WriteExpression( "Caption","=DTOC(DATE())")
See also READEXPRESSION().
WriteMethod( MethodName, MethodText)
Description: Design-time only method that writes the specified text to the specified method.
Parameters:
MethodName: Specifies the name of the method to write the specified text to.
MethodText: Specifies the text to write to the specified method to.
Note: WRITEMETHOD( Property,"") is equivalent to "Reset to Default" for events and methods.
Strange as it seems at first, we use WRITEMETHOD to concatenate strings to build method code at design time. Use "CHR(13)" wherever you need a new line, and it's good practice to use "CHR(13)" at the beginning and end of the streams so to avoid unexpected line combinations.
This program appends code to the Form.Click event to bring-forth a "Hello World" dialog.
* AppendClick.PRG
* Skeleton Builder demonstrating
* adding "Hello World" to Form.Click
PARAMETERS toPassedObject, tcContextString
LOCAL loForm, lcMessage
*-- Create the Hello World Code
*-- Begin and end with a CR for safety's sake
lcMessage= CHR(13)+ ;
"=MESSAGEBOX('Hello World')"+ ;
CHR(13)
*-- Locate the form object
loForm= toPassedObject
DO WHILE loForm.Baseclass <> "Form"
loForm= loForm.Parent
ENDDO
*-- Append the Hello World code to any existing code
*-- in the Click method
loForm.WriteMethod( "Click", ;
loForm.ReadMethod("Click") + lcMessage)
Conclusion
It doesn't take a genius to recognize the phenomenal potential offered to us by Builders and Wizards. The next few months will be exciting as new tools, managers, and possibly entire frameworks will be invoked and applied through Builders, Wizards, and related hooks.
From this document, the following things to retain: