What are Dynamic Forms?

Dynamic Forms is a new way to create forms based on a "markup syntax" similar to HTML and XAML, in which you specify both the layout and ControlSources for the various controls to be displayed on the form.

The result will be a dynamically constructed form, specified in code, rather than manually creating it in the Form Designer.

Source code

The source code for this class is a single PRG file which contains the class definitions needed to create Dynamic Forms in your apps, and also includes as a ready-to-run sample to display a rendered form sample.

The procedural code at the top of the source code PRG serves as an example of how to use the DynamicForm class at run time to dynamically render a live FoxPro form to view and edit properties on an object. As listed below, you can also bind to Fields on
Cursors/Aliases/Tables/Views as well as defined Private or Public variables.

Basic Usage

Step 1: Working with Cursors - Open and arrange the cursor(s) that you want to display on the form. You can reference more than one Cursor/Alias/Table/View in your layout. Be sure to locate the
record pointer(s) to the correct record(s) before launching the form.

Working with an Object – Create or populate an object with various properties (aka oDataObject).

Working with Variables – Declare and define variables as Public or Private variables to bind the form control to.

Set the cBodyMarkup property with your markup syntax string from step 2:

loForm.cBodyMarkup = <your_markup_string>

For cursors, set loForm.cAlias to the alias to which your layout will bind.
For objects, set loForm.oDataObject to the object reference to which you layout will bind.
For Public/Private, no additional settings are required. Just use the variable name in ControlSource field of the layout.

Note: Render() is an optional step which can give you a return value to test if everything rendered okay. You can skip this step and go straight to the Show() method per step 6, if you do not wish to take this preliminary step in the process

The return value indicates if everything rendered without errors. (Any errors usually caused by errors in your cBodyMarkup markup string.)

If the Render() methods returns .F., this means there were rendering errors, which you can read from loForm.oRenderEngine.nErrorCount and get details on every error from the
loForm.oRenderEngine.oErrors collection, or the error list in a string format by calling
loForm.oRenderEngine.GetErrorsAsString().

Even with rendering errors, the form can still be displayed to the users (as shown in step 6) if desired. During development, showing the form (even with errors) will help you see where the errors occurred, as it will show a red error container in place
of any controls which had rendering errors.

Step 6: Call
loForm.Show() to display the form to the user:

loForm.Show()

By default, a Modal form is created, but this can be a Modeless form by passing a value of zero as the first parameter to Show().

At this point, the user is interacting with the form, and it will eventually be closed when they click Save, Cancel, or the [X] button. At that time, when using a Modal form, flow will return to the calling code where you can read any property on loForm and
loForm.oRenderEngine, and even access the rendered controls, and continue processing per step 7. For details about Modeless forms, see the Modeless Forms section under the Show() method documentation link below.

Binding form controls to data

Besides visual layout, the markup syntax (shown in the next section) first allows you to specify the ControlSource for each control, linking it to table fields, object properties, or Public/Private variables that you want to display and bind to in your dynamically
generated form.

Typical data sources are the same as what you would use in the Form Designer or at run time in your current apps, which can include:

All or selected fields from one or more Cursor/Alias/Table/View

All or selected properties on an object. (I.e. an oDataObject from a table row, or other populated object)

Private or Public variables

Any combination of the above

Binding to Aliases
For aliases, set loForm.cAlias to the alias to which your layout fields will bind, or include the alias name in the ControlSource for each field.

If you are working with multiple aliases, you will need to include the target alias names as part of the ControlSource setting in the markup.

MyAlias1.some_field

MyAlias2.other_field

Binding to Objects
For objects, set loForm.oDataObject to the object reference to which your layout fields will bind.

Binding to variables For Public/Private, no additional settings are required. Just use the variable name in ControlSource field of the layout.

Markup Syntax

The form layout is driven by a custom "markup syntax” which is defined in a text string. The markup syntax contains the list of properties/fields/variables that you want to display and bind to in the generated form. It is similar to HTML markup.
Once you have defined your form layout, assign it to the cBodyMarkup property on the oRenderEngine.

[controlsource] is not surrounded by any ' or " or [ ], it's just the field, property, or variable name.

attributes

Each attribute corresponds to a native FoxPro property on the UI control, or a custom attribute related to controlling the flow of UI controls on the rendered form.

Numeric and Boolean attribute values may be surrounded with single quotes or double quotes, if desired.

Expressions can be used in attribute values if they are surrounded in parentheses, and these will be evaluated at run time to derive the value.

There are several special attributes to adjust the flow or layout of controls on the rendered form. See
special attributes section for details.

Executing code from markup during the render cycle:

You can also execute single lines of FoxPro code at any point during the rendering of the fields in the markup string. Simply include the code line(s) wrapped in parentheses and separated by the Pipe character delimiter, just like all other field definitions.

Other notes:

Commas are optional between each name/value pair in the markup string.

Notice that '|' is the default delimiter between each field definition in the cBodyMarkup string.

After you define the layout markup string, assign it to the
cBodyMarkup property on the form:

loForm.cBodyMarkup = lcMyBodyMarkupString

Additional supported feature in the Markup syntax

As of ver 1.4.0 and later, you can also set any property of the Render Engine, Form, or render Container in the markup block. See
this page for more details.

This allows you to entirely configure and drive all properties from the markup, without having to do it through your FoxPro code. A nice feature for anyone who wants to store fully configured form definitions in a lookup table, or those who prefer the markup
technique better than the code technique.

Adding styling attributes to the markup In addition to specifying the controlsources, you can also include styling attributes to control all native properties on the generated control, or create other effects to control the rendered form view:

Once you have defined your markup layout, pass it to the RenderEngine like this:

loForm.cBodyMarkup = "...layout markup text..."

Attributes

Each markup "attribute" maps to a native FoxPro property by the same name and they are applied just as you would do in FoxPro code or in the Properties window. Every native FoxPro property is supported, as well as custom properties if you are using
a custom class. Please report any issues if you find one that is not support or interpreted properly.

If you reference a custom class in the :class attribute, you can also specify its .classlibrary attribute (only required if the classlibrary or procedure is not already in place via Set ClassLib or Set Procedure).

Special Attributes

Additionally, the following special attributes are supported to control layout and flow of the controls:

(Optional) Most forms can be rendered in a single “column”, but you can move the flow to the top of the next “column” by specify a column number to generate the control
into. The default column is 1, but the RenderEngine will increment to the next column once the render container reaches the max height specified in the
oRenderEngine.nColumnHeight property.
You can also advance the column location yourself by including this attribute on any one control, and then flow of remaining controls continues in the specified column, and will wrap to the next column if
oRenderEngine.nMaxHeight is reached.

.margin-top = n

Adds additional spacing between the previous row and the current row, so that the current is pushed down from the previous.
'n' is the number of pixels

.margin-bottom = n

Adds additional spacing below the current row (after it's rendered), so that the NEXT row will be pushed further down.
'n' is the number of pixels

.margin-left = n

Adds additional to the left of the control so as to create more space between the previous control or column left edge.
'n' is the number of pixels

.row-increment = n

Instructs the engine to skip down 'n' number of rows (default is 1).
The row height is controlled by the nVerticalSpacing property on the RenderEngine.
'n' is the number of rows to skip down in the render flow.
Tip: To render the current control on the same row as the previous control,
use .row-increment = 0 as shown here:

Conditional Rendering

.render-if =

You can include .render-if = (SomeExpressionOrFunctionCall()) to conditionally render the control.

Setting Focus

.set-focus = .t.

This attribute, when used on a field markup, indicates this control is to have focus when form is shown.

Absolute positioning

You can achieve absolute positioning of any control using the native FoxPro Top and Left properties as attributes:

.top = n
.left = n

Once a control has been rendered using an absolute position, the next control in cBodyMarkup will resume rendering flow from this last render location.

Function calls or in attribute values

You can use function calls for any attribute value, as long as the function returns to correct data type for the targeted property. Wrap the expression in parentheses as shown here:

Examples:

.caption = (GetCaptionForBlah())

.enabled = (UserIsAdmin())

.tooltiptext = (pcSomePrivateStringVar)

Field Labels

Field labels will be automatically added for each UI controls that is bound to an property, field, or variable.

1. The default Caption for each label is the name of the property/field/variables specified in the control source.
2. Underscore characters in the property/field/variable names will be converted to a space for cleaner caption text.
3. You can override the label caption by using .label.caption = 'Blah' attribute.
4. You can prevent a label from being displayed by setting .label.visible = .f.
5. You can set ANY FoxPro property on the label by using
'label.' a prefix on the attribute:
i.e:

6. By default, field labels are rendered to the left of the input control (inline with).
Set loForm.oRenderEngine.lLabelsAbove = .t. to render the labels ABOVE the input controls:

Return values from the form

You can get custom return values from the Dynamic Form using instances of the default “commandbutton” class, which resolves to the custom
DF_ResultButton class by default. Using these command buttons, when the button is clicked, the form will hide and the caption of the command button will be passed to the
loForm.cReturn property (any hotkey characters in the caption will be removed). This allows you to easily test
loForm.cReturn to see which of command button was clicked, and you can take the appropriate action as control passes back to the calling program.

Actually, the caption is passed to the Tag property on the button, and the button click event passes its Tag value back to
loForm.cResult and then hides the form. So, if needed, you can set the Tag value directly if you do not want to use the caption as the return value.

Custom UI control classes

By default, native FoxPro baseclasses (textbox, label, checkbox, etc.) are used on the form. If you wish to specify your own custom classes to use instead, the following properties allow you to change the defaults for all instances on the form:

You can also specify :class and
:classlibrary inline on each control in the field .

SomeValue .class = 'MyTextBoxClass' .classlibrary = 'MyClassLib.vcx'

Rendering Errors

A return value from calling loForm.Render() indicates if everything rendered without errors. Any rendering errors are usually caused by errors in your cBodyMarkup markup string. The error messages and rendered form should
help you identify which control(s) had the errors.

If the Render() methods returns .F., this means there were rendering errors, which you can read from the
nErrorCount property:

loForm.oRenderEngine.nErrorCount

and you can get details on each error from the oErrors collection:

loForm.oRenderEngine.oErrors

You can also call the oRenderEngine.GetErrorsAsString() method to get the errors in a string format which can be easier to read than the working with the
oRenderEngine.oErrors collection. You can see a sample of the error test and this method call in the source PRG file (in the sample procedural code at the top of the file).

Even with rendering errors, the form can still be displayed, but the error controls on the form are really intended to be a Developer tool to help you find bugs in your markup. It will show a red error container in place of any controls which had rendering
errors.

Release history

2012-10-18 = Alpha 1.5.0 released.

2012-10-26 – Alpha 1.4.1 released.

2012-10-23 – Alpha 1.4.0 released.

2012-10-08 – Alpha 1.3.0 released.

2012-09-25 – Alpha 1.2.0 released.

2012-09-18 – Alpha 1.0.0 released.

2012-09-04 – Public alpha release 0.9.0 released on VFPx.

2012-08-31 – Dynamic Forms accepted as an official VFPx Project.

2012-08-24 – Project proposal submitted to VFPx admins.

2012-05-08 – Initial concept class created and the first ever Dynamic Form was generated from a few lines of code.

Comments

The documentation for Dynamic Forms is very nice. It's really handy to have all the documentation in the procedure form itself. Nice to see the glory of text markup returning via this simple, but powerful interface. I'm going to explore its use in my webpage development. There's a strong similarity to CSS coding which is very cool to see added to VFP. Loved your introductory video. Nice work indeed.

Why wouldn’t you use xml for the markup? In complex scenarios it will be far easier to read. Schema validation could be useful and it would be easier to parse / create tools for. Thiers a reason Microsoft’s WPF and Java’s JFX use xml.