VFP 9.0 Reporting System Fundamentals
By Colin Nicholls
This article was first made available as session notes to OzFox 2007 attendees.
One important thing about Visual FoxPro 9.0 is that it is the first version
in which fundamental components of the reporting system have been
implemented in the FoxPro language itself. FoxPro has always featured an
extensible development environment, and has always relied on Xbase
components, ever since GENMENU.PRG and its brethren. GENMENU at least is
still used today. Up until version 9.0, the reporting engine in FoxPro has
always been a "black box", implemented in Visual C, with the internals
hidden from view. As a result of the 9.0 re-write (in Visual C++ and
FoxPro), a new array of techniques are available to the developer to create
new kinds of report output.
Prior to the commencement of the beta program for VFP 9.0, a rumor hit the
user forums that Microsoft would be over-hauling the Reporting System in VFP,
and developers would finally get what they've wanted since version 3.0: an
object-oriented report designer.
Almost inevitably, no two developers seem to have the same idea of how an
object-oriented report engine would behave, once you get past the initial
specification of "you know, it's like the current report designer, only with,
like, objects", and into the devilish details of implementation.
Microsoft did not provide an object-oriented reporting system in VFP 9.0.
Instead they provided maximum functionality and extensibility, in a hybrid
system that gives developers all the power and benefits of object-orientation
that they want, along with the all the backwards-compatibility that they really
This hybrid system is what we call object-assisted reporting. And hopefully,
by the end of this article, you'll understand why that's an apt term, and
exactly how it works.
In Visual FoxPro 9.0, the reporting engine outsources some fairly major
portions of functionality out to FoxPro applications:
- ReportBuilder.App handles the user interface of the Report Designer
- ReportPreview.App handles the user interface of the Report Designer's
- ReportOutput.App operates to support and allow simple and
straight-forward command syntax to invoke the power of the objectâ€“assisted
New system variables
Because it's FoxPro, we are able to replace the default components with our
own custom applications, if we should decide we need to. We can do this because
each application is specified via its own special system variable:
In this whitepaper, we will cover how the object-assisted reporting system
performs a report run. But first, let's take a look at how the Report Designer
and Report Builder application work together.
1. The Reporting System at Design Time
In VFP9, the Report Designer is mostly unchanged from VFP 8.0. Menu options
have been moved around a bit, and there are some cosmetic changes such as
resizing handles appearing when the mouse pointer moves over them, but
essentially it is the same design surface.
The biggest change is that it will delegate the task of displaying dialog
boxes to an external application, if one has been specified in the
_REPORTBUILDER system variable. This allowed Microsoft to develop
completely new Properties dialogs for the Report Designer, with more
functionality than they would otherwise have been able to do with the resources
System Variable: _REPORTBUILDER
When we double-click on a report layout element, or select an option from
the Report menu, the Report Designer looks at _REPORTBUILDER
and, if there is a program or application specified, calls out to that
program to service the event.
It is important to understand that if _REPORTBUILDER is empty,
then the Report Designer will display the familiar "old-style" native dialogs.
Any program assigned to _REPORTBUILDER must support the Report
Builder API. We can refer to them as "report builder" applications.
It is important to understand that the "old-style" dialogs are still
What is the Report Builder API?
The Report Builder API is a contract between the Report Designer and a
program or application assigned to _REPORTBUILDER. It consists of:
- 19 different points at which the Report Designer will invoke the report
- 4 parameters passed to the report builder during each event
- 2 binary flags returned from the builder to the Report Designer so that
it can take appropriate action.
What happens during a Report Designer Event
The Report Designer:
- checks for a valid program or application specified by
- creates a private data session;
- buffers the underlying source table of the report currently being edited
as a cursor open in the new data session with the alias "FRX";
- locates the record pointer on the appropriate record in the FRX cursor;
- invokes (_REPORTBUILDER) with 4 parameters.
Report Builder Parameters
Let's examine each of these parameters in detail.
A numeric value of -1 is passed (by reference) to the report builder program
as a placeholder for returning values back the Report Designer. More about this
This parameter contains a numeric value that specifies what
particular event has occurred: Was it a properties dialog box? A selection from
the menu? Was a report layout opened for editing in the Designer? The possible
values of this parameter are documented in the Help file and also in the
FFC\foxpro_reporting.h header file:
*-- FRX Report Builder event types
#define FRX_BLDR_EVENT_PROPERTIES 1
#define FRX_BLDR_EVENT_OBJECTCREATE 2
#define FRX_BLDR_EVENT_OBJECTREMOVE 4
#define FRX_BLDR_EVENT_OBJECTPASTE 5
This parameter is an object reference. The object is actually an instance of
the FoxPro base class Empty, with additional properties added to
it that indicate what clauses were used on the original FoxPro command line that
launched the Report Designer (among other things).
The exact properties are documented in the Help file. As we'll see, having
these clauses available to our programs is very useful.
This parameter specifies the session ID of the data session that the Report
Designer is running in. This allows the report builder application to switch
back and forth between data sessions so as to interrogate any data tables open
in the Report Designer session - either opened manually or automatically by the
report's Data Environment.
What a report builder must do
Due to the call and response mechanism of Report Designer builder events, the
report builder process must be a modal one. It has all the information it
requires in order to read from the FRX cursor, interrogate the environment of
the Report Designer, take action based on the parameters passed to it, and make
changes to the FRX cursor. It then terminates, returning control to the Report
At that point, the Report Designer needs to know two things:
- Has the report builder process handled the event, or should the native,
original behavior be allowed to take place?
- Does the report layout need to be refreshed from changes made by the
builder program to the FRX cursor?
The Report Designer can get this information by looking at the
returnFlags parameter, which - being passed by reference - can have its
value set by the report builder program.
About that returnFlags parameter
The Report Designer assumes that the value placed in returnFlags by the
builder program is a sum of two individual binary flags:
||Instruction to Designer
||Suppress the native action - if
any - that would normally be associated with the Designer event.
||Respect changes made to the
buffered FRX cursor by re-loading the report layout.
returnFlags = 0 = 000
Neither flag is set, so the behavior of the Report Designer will be the same
as if there was no report builder program assigned to _REPORTBUILDER.
In other words, any changes made to the FRX cursor will be ignored, and the
Report Designer will behave as in VFP 8.0 and earlier versions.
returnFlags = 3 = 011 = 1 + 2
Both flags are set, so the Report Designer will refresh the report layout in
the design window, reloading any changes made to the FRX cursor. Also, if there
is any "native" behavior attached to the event that occurred, the Report
Designer will prevent it from taking place.
This process is best understood by looking at a simple example that allows us
to choose the values of the return flags.
Consider the following program:
PARAMETERS returnFlags, eventType, commandClauses,
WAIT WINDOW ;
"eventType = "
+ TRANSFORM( eventType )
+ chr(13) + ;
"Data session = " + TRANSFORM(
SET("DATASESSION")) + chr(13) + ;
"ALIAS() = "
+ chr(13) + ;
"RECNO() = "
+ TRANSFORM( recno("frx")) + chr(13) + ;
"frx.OBJTYPE = "
+ TRANSFORM( frx.OBJTYPE) + chr(13) + ;
"frx.OBJCODE = "
+ TRANSFORM( frx.OBJCODE)
returnFlags = 0
This example performs a WAIT WINDOW command, listing certain
interesting values at each Designer event that occurs. Save this program
as, say, SIMPLEBUILDER.PRG and assign it to _REPORTBUILDER:
_reportbuilder = "SimpleBuilder.prg"
Now we should see as you click around the Report Designer, that each event is
high-lighted with our little WAIT WINDOW. Try right-clicking and selecting
Properties... Try selecting Variables... from
the Report menu.
Aside: ReportBuilder.App's Event Inspector
We can get an even more comprehensive view of a Designer event by using the
Event Inspector feature of the default Report Builder. The Event Inspector can
be accessed from any Report Builder dialog's right-click context menu:
All the information available to the builder application is laid out for our
review in a messagebox:
As we'll see shortly, the default builder, ReportBuilder.App, doesn't
respond to every possible event, so sometimes it is handy to use a simple
builder like the one shown in the example above.
Consider the following program:
PARAMETERS returnFlags, eventType, commandClauses,
"Event: " + TRANSFORM( eventType ) + chr(13) + ;
IF MESSAGEBOX( ;
"RECNO('frx') " + TRANSFORM( RECNO("frx")) + CHR(13) + ;
"frx.OBJTYPE: " + TRANSFORM( frx.OBJTYPE) + ;
chr(13) + chr(13) + "Allow this event?" ,4)=6
returnFlags = 0 && clear both flags, allow default action to proceed
returnFlags = BITSET( m.returnFlags, 0 ) && NODEFAULT : suppress event
This example demonstrates the effect of changing the "NODEFAULT" return flag,
using a simple MESSAGEBOX() Yes/No call. Try it out in the Command
_reportbuilder = "EventAlert.prg"
MODIFY REPORT _samples+"\Solution\Reports\Colors.frx"
We will see the report builder program responding to the first event
triggered during a report design session - which happens to be the "report open"
event. What will happen if we select "No" to tell the Report Designer to
suppress its native behavior?
If you thought that the Report Designer would close, don't feel bad. It's a
trick question. There doesn't happen to be any native Designer behavior
associated with the "report open" event. We can't prevent the report from being
opened for editing.
Now select Close from the File menu to close the Report
Designer window. An event type of 8 is triggered. What will happen if we choose
"no" this time? Well, it turns out that we can prevent the Report Designer from
closing if we set the NODEFAULT return flag in response to event type 8.
Now select Optional Bands... from the Report menu. An event
type of 11 is triggered. We can prevent the Optional Bands
dialog from appearing if we set the NODEFAULT return flag.
We have now observed the following:
- Report builder programs can prevent the Report Designer from responding
to certain actions taken by the user during a report design session.
- Not all actions can be suppressed - some events do not have associated
Consider the following program:
PARAMETERS returnFlags, eventType, commandClauses, designerSessionId
returnFlags = 0
IF eventType = 7 AND commandClauses.IsCreate
IF MESSAGEBOX("Do you want to use a template?",4)=6
cReportFile = GETFILE("FRX",
IF NOT EMPTY( m.cReportFile )
SET SAFETY ON
BITSET( m.returnFlags, 1 ) && refresh layout
First, notice that this example takes no action unless a report layout has
just been opened for editing (event type = 7) and it is a newly
created layout (commandClauses.IsCreate = TRUE). The program
filters the kinds of designer events that it will respond to. If these
conditions are met, the program prompts you to select an existing FRX file that
it then copies in to the new layout, using standard syntax (ZAP,
USE, APPEND FROM, etc). With the FRX layout exposed as
a read-write cursor, there is nothing stopping you from wreaking terrible
vengeance on the contents, including ZAP and APPEND FROM.
It really is that simple. All we have to remember to do is set the "REFRESH"
bit (returnFlags parameter to 2) to ensure that the Report Designer respects the
changes made to the FRX cursor - in this case, a wholesale replacement of the
contents! Try it out:
_reportbuilder = "TemplateChooser.prg"
If you choose a source report that contains members in its Data Environment,
you will notice that this process duplicates these as well.
VFP 9.0 ships with a default report builder application:
ReportBuilder.App. By default, the _REPORTBUILDER system variable should be
_reportbuilder = HOME()+"ReportBuilder.App"
The ReportBuilder application integrates with the Visual FoxPro Report
Designer using the same report builder API that we explored in the examples
above. It replaces the native dialog boxes with alternate versions, implemented
in FoxPro itself. As well as being more aesthetically pleasing and ergonomically
designed, these new dialogs expose additional functionality that is not
available in the native dialog boxes.
Along with the other component reporting applications, ReportBuilder.App is
redistributable with our applications. We also get the source to
ReportBuilder.App in the XSOURCE.ZIP file located in the Tools\ subdirectory in
Visual FoxPro's home directory.
It's data-driven by a configuration table
Just like the Form and Class Wizards and Builders in previous versions of
Visual FoxPro, the ReportBuilder application uses a table of class definitions
mapped to specific Report Designer events. This "event handler registry" table
is stored as a read-only lookup resource inside the compiled ReportBuilder
In normal operation, after being invoked in response to an event in the
Report Designer, the ReportBuilder application looks at the currently selected
record in the FRX cursor; and uses the OBJCODE and OBJTYPE values together with
the event type parameter, and performs a lookup operation in its registry table
to obtain the name of a class to instantiate to handle the event.
Additional ReportBuilder.App functionality
String Trimming mode
Speaking of additional functionality, the ability to specify the string
trimming mode on Field/Expression layout elements is new to VFP9. It is possible
because in VFP9 report printing and previewing uses GDI+ graphics API instead of
the older GDI API used in previous versions of FoxPro.
If we SET REPORTBEHAVIOR 80, we can print and preview
using the older GDI API in Visual FoxPro 9.0 as well. This non-object-assisted
mode has some advantages (such as speed) but any advanced formatting features
(such as string trimming mode) won't be available, of course.
Let's take a look a report as it would look VFP 8.0:
SET REPORTBEHAVIOR 80
REPORT FORM fileexpr.frx PREVIEW
This report (fileexpr.frx) shows the results of some common
FoxPro functions. However, the Field/Expression control used on the right is not
big enough to contain the full text of the evaluated result. As we can see, the
expression is truncated "to nearest word", with no clue that we are not seeing
the full text.
GDI+ allows several different ways that a truncated string can be expressed.
The ReportBuilder application exposes these settings in the Format tab of the
new Field/Expression Properties dialog box.
You might expect the default mode to be Trim to nearest word
so as to ensure backward-compatibility with earlier versions of FoxPro. It's
not. "Default Trimming" actually means "Use whatever the default string trimming
mode of the ReportListener class that is processing the report". And, by
default, that is Trim to nearest word, append ellipsis.
We chose this report because it is also ideal for showing one of the more
interesting string trimming modes available under GDI+. See what happens when we
change the trim mode of the field/expression control to Filespec: show
inner path as ellipsis.
Now both the head and tail of the text expression is shown. This works with
any string, not just file specifications!
Browsing the FRX structure
As a developer, you may encounter complex report layouts that don't work the
way you expect, and have to resort to browsing the .frx source table in order to
debug the problem. The ReportBuilder includes a nice FRX browser dialog for this
DO (_reportbuilder) WITH 2, _samples+"\Solution\Reports\colors.frx"
TIP: ReportBuilder.App has many command-line
parameters. See Appendix A for a complete list (as of SP2).
Recap: The structure of the FRX table
If you are not familiar with the structure of the FRX source table, you
should note that:
- Each report element, including columns, bands (title, header, detail,
footer, etc) is represented by a separate record in the table.
- Each report element type is identified by the values in the OBJTYPE and
- If a report layout element is currently "selected" in the Report
Designer, the corresponding record will have the CURPOS field set TRUE.
Accessing the ReportBuilder Options dialog
The ReportBuilder Options dialog box is accessible from any ReportBuilder
dialog by using the right-click context menu. Alternatively, if we do not happen
to be editing a report layout, we can get there directly by running the
ReportBuilder.App from the Command window:
DO (_reportbuilder) [ WITH 1 ]
The parameter 1 is optional.
From here we can configure how the ReportBuilder.App operates.
TIP: The ReportBuilder's Options
dialog is not available when a report is being edited in PROTECTED mode.
The ReportBuilder's Event Handling mode
In addition to the "normal" mode of looking up a handler class in its
registry table, the ReportBuilder application has three other modes of handling
Handle Mode: Debug
In this mode, the ReportBuilder application presents a special "debug"
dialog. We can choose how to set the returnFlags parameter for every Report
Handle Mode: Event Inspector
We described the ReportBuilder's Event Inspector earlier. In
this mode, the Event Inspector window is displayed for every
Report Designer event, similar to the Debug handler, although as
it is a MESSAGEBOX() dialog, you obviously have no
choice about the return flags.
Handle Mode: Ignore builder events completely
In this mode, the ReportBuilder application lets every event pass through
unhandled, as though it were not assigned to _REPORTBUILDER at all.
Because it is a modal process, the ReportBuilder application saves
its settings between invocations in a public variable - actually a member
object of _SCREEN called "ReportBuilderData":
This object is created the first time ReportBuilder.App is invoked.
It does not survive a CLEAR ALL command.
The ReportBuilder application does not save its settings between
sessions of Visual FoxPro.
Other ReportBuilder.App features
With the default ReportBuilder application plugged in to the Report Designer,
we get the following additional features:
- We can edit the contents of the USER field in the FRX source table
- We can store and edit structured meta data with each object
- We can copy a data environment from an existing report
- We can link a report to a visual data environment class
- There's a new Multiple selection dialog
- We are able to configure Protection flags
- We can assign "design-time" captions to field/expression objects
- We can assign Tooltips to any layout element
- We have greater control over the precise location of report layout
How ReportBuilder.App uses the resource file
The ReportBuilder.App saves selected window layout and preference information
in the resource file, if SET RESOURCE ON. These records can be
identified by the following column values:
||Size and position of the Configuration table browser.
||Size, position and font of the text editor window.
||Size and position of the memberdata browser.
||Size, position and font of the XML editor window.
||Size and position of the FRX browser.
This article continues in