Chapter 6
Extending the Reporting System at Design Time

Among the new and improved features in the reporting system of VFP 9 is the ability to extend the Report Designer to provide easier-to-use, more powerful, and more flexible report writing for your development team and even your end-users. In this chapter, you will learn about the new Report Builder application, how it captures and handles events raised by the Report Designer, and how you can create your own handlers to extend the VFP Report Designer in ways you never thought possible.

Earlier versions of VFP provided very little means of customizing the Report Designer, other than perhaps the appearance of the window it appeared in. One of the design goals for VFP 9 is to provide a mechanism for hooking into the behavior of the Report Designer to customize its appearance and behavior as much as possible. This is implemented by having events in the Report Designer, such as opening a report or adding an object, passed to an Xbase component that can take any action necessary when events occur.

The new system variable _REPORTBUILDER points to an application (referred to in this chapter as “the report builder application”) that receives notification about events from the Report Designer. When a design-time report event occurs and _REPORTBUILDER points to an existing application, the Report Designer creates a private data session, opens a copy of the FRX currently being edited in that data session, and then calls the report builder application. By default, _REPORTBUILDER is set to ReportBuilder.APP in the VFP home directory, but you can substitute another application if you wish. If you specify a non-existent application, no error occurs but events are not raised. Any application specified by _REPORTBUILDER must be modal in nature because the Report Designer expects to call it and receive a return value indicating what happened.

The Report Designer passes the parameters shown in Table 1 to the report builder application when an event occurs.

Table 1. The parameters passed to the report builder application by the
Report Designer.

Parameter

Type

Description

ReturnFlags

N

Passed by reference with an initial value of -1. Used to return values to the Report Designer.

EventType

N

An integer representing the event that occurred.

CommandClauses

O

An object (based on the Empty baseclass) with properties indicating the clauses used in the CREATE/MODIFY REPORT command.

DesignerSessionID

N

The data session ID of the Report Designer.

 

ReturnFlags is used to return a value to the Report Designer. The possible return values are bit flags that can be summed. Adding 1 means the event was handled by the report builder application so the Report Designer’s normal action is suppressed (sort of like using NODEFAULT). Adding 2 indicates the report builder application made changes in the FRX cursor so the Report Designer should reload the changes into its internal copy of the FRX.

EventType contains a value identifying the event that occurred. Table 2 shows the possible values, along with the type of event (a report event, an object event, or a band event). The table also indicates whether adding 1 to the ReturnFlags parameter can suppress the event. “Must delete record” means a newly created object’s record has already been added to the FRX, so to suppress the creation of an object, you must delete its record in the FRX cursor and set the ReturnFlags parameter to 3 (the event was handled and changes should be reloaded).

Table 2. The EventType parameter contains one of these values indicating the type of report event.

Value

Description

Type

Can Suppress

1

A Properties dialog (for the report, a band, or an object) is being invoked.

Report,
Object,
Band

Yes

2

An object or band is being created.

Object,
Band

Yes (must delete record)

3

Reserved for future use.

 

 

4

An object or band is being removed.

Object,
Band

Yes

5

One or more objects are being pasted into the report from the clipboard. Each pasted object has the CURPOS field set to .T.

Object

Yes (must delete records)

6

The report has been saved.

Report

No

7

The report has been opened.

Report

No

8

The report is to be closed.

Report

Yes

9

The DataEnvironment is to be opened.

Report

Yes

10

The report is to be previewed.

Report

Yes

11

The Optional Bands dialog is being invoked.

Report

Yes

12

The Data Grouping dialog is being invoked.

Report

Yes

13

The Variables dialog is being invoked.

Report

Yes

14

Ctrl-E was pressed on a label object.

Object

Yes

15

The Grid Scale dialog is being invoked.

Report

Yes

16

One or more objects are being created by a drag-and-drop operation from the DataEnvironment, a DBC, or the Project Manager. This event fires once for each object being created, and if a label is created, once for each label as well.

Object

Yes (must delete record)

17

“Load data environment” was selected from the Report menu.

Report

Yes

18

The report is to be printed.

Report

Yes

19

“Quick Report” was selected from the Report menu.

Report

Yes

 

CommandClauses has the properties shown in Table 3.

Table 3. The properties of the CommandClauses object provide information about how the Report Designer was invoked.

Property

Type

Description

AddTableToDE

L

.T. if the Add Table to DataEnvironment option was turned on in the Quick Report dialog.

Alias

L

.T. if the Add Alias option was turned on in the Quick Report dialog or the ALIAS clause was specified in the CREATE REPORT FROM command.

FieldList

O

A collection of numeric field numbers representing the fields specified in the Quick Report dialog or the FIELDS clause of the CREATE REPORT FROM command.

File

C

The file name of the FRX open in the Report Designer. This file may not actually exist if CREATE REPORT/LABEL was used.

Form

L

.T. if a form layout was chosen in the Quick Report dialog or the FORM clause was specified in the CREATE REPORT FROM command; .F. if a column layout was chosen or COLUMN was specified.

From

C

Contains the table name specified in the CREATE REPORT FROM command.

InScreen

L

.T. if the IN SCREEN clause was specified in the CREATE/MODIFY REPORT/LABEL command.

InWindow

C

The name of the window specified in the IN <window> clause of the CREATE/MODIFY REPORT/LABEL command.

IsCreate

L

.T. if the command was CREATE REPORT/LABEL; .F. if the command was MODIFY REPORT/LABEL.

IsQuickReportFromMenu

L

.T. if the Quick Report function was invoked.

IsReport

L

.T. if the command was CREATE/MODIFY REPORT; .F. if it was CREATE/MODIFY LABEL.

NoEnvironment

L

.T. if the NOENVIRONMENT clause was specified in the MODIFY REPORT/LABEL command.

NoOverwrite

L

.T. if the NOOVERWRITE clause was specified in the CREATE REPORT FROM command.

NoWait

L

.T. if the NOWAIT clause was specified in the CREATE/MODIFY REPORT/LABEL command.

Protected

L

.T. if the PROTECTED clause was specified in the CREATE/MODIFY REPORT/LABEL command.

Save

L

.T. if the SAVE clause was specified in the CREATE/MODIFY REPORT/LABEL command.

Titles

L

.T. if the Titles option was turned on in the Quick Report dialog or the TITLES clause was specified in the CREATE REPORT FROM command.

Width

N

The number of columns specified in the CREATE REPORT FROM command (-1 if this clause was omitted).

Window

C

The window name specified in the WINDOW <name> clause of the CREATE/MODIFY REPORT/LABEL command.

 

The report builder application runs within a private data session that contains the FRX cursor. The Report Designer passes its own data session ID to the report builder application
in case the report builder needs to access the tables open in the DataEnvironment of the
Report Designer.

The FRX cursor the Report Designer creates for the report builder application has the alias “FRX.” The record pointer is on the record for the object the event occurs for; this is the report header record (the first record in the cursor) if it’s a report event rather than an object event. The records for any objects selected in the Report Designer have the CURPOS field set to .T. There’s one slight complication with this: because the report header record uses CURPOS to store the value of the Show Position setting, you should ignore this record when looking at records with CURPOS set to .T. For example, to count the number of selected objects, use:

count for CURPOS and recno() > 1

ReportBuilder.APP

By default, _REPORTBUILDER is set to ReportBuilder.APP in the VFP home directory. This application provides a framework for handling design-time report events, plus it includes a set of more attractive and functional dialogs that replace the native ones used by the Report Designer (as discussed in Chapter 5, “Enhancements in the Reporting System”). You can distribute ReportBuilder.APP with your applications to provide its behavior in a run-time environment; see the “How To: Specify and Distribute ReportBuilder.App” topic in the VFP help for details.

In addition to the Report Designer calling it automatically, you can call ReportBuilder.APP manually to change its behavior for the current VFP session. It doesn’t write settings to any external location, such as a table, INI file, or the Windows Registry, so, with one exception you will see in a moment, state isn’t preserved from session to session.

·         If you call it with no parameters or pass it 1, it displays an options dialog (see Figure 1) where you can change its behavior. You can also right-click any of the properties dialogs and choose Options to launch the Options dialog.

·         Pass 2 and optionally the name of an FRX file to browse a report. Right-click the properties dialogs and choose Browse FRX to do the same thing for the current report.

·         Pass 3 and the name of a DBF file to use that DBF as the handler registry or a blank string to use the internal handler registry table. (The handler registry is discussed later in this chapter.)

·         Pass 4 and a numeric value to set the “handle mode.” The numeric values match the buttons in the When handling Report Designer events, the builder will setting shown in the options dialog. For example, DO (_REPORTBUILDER) WITH 4, 3 tells ReportBuilder to use the event inspector.

·         Pass 5 and optionally the name and path of a file to create a copy of the internal handler registry table (if you don’t pass the second parameter, you’ll be prompted for the table to create).

Figure 1. Configure the behavior of ReportBuilder.APP using the Report Builder Options dialog.

The Options dialog has the following features:

·         You can define what happens when ReportBuilder.APP receives a report event. The choices are to search for a handler class in the handler registry table (the default behavior), use a “debug” handler for events (displays a dialog showing the FRX in a grid and allows modifications to it and other settings), use an “event inspector” handler (displays information about the event and the FRX in a MESSAGEBOX() window), or ignore report events. You can also display the event inspector window by right-clicking any properties dialog and choosing Event Inspector.

·         You can specify what handler registry table to use, or make a copy of the internal one (the handler registry is discussed in “Registering report event handlers,” later in this chapter). You can also browse the selected registry table.

Here are some ways you can extend the functionality of the Report Designer.

·         Replace ReportBuilder.APP with your own report builder application by changing _REPORTBUILDER.

·         Wrap ReportBuilder.APP by changing _REPORTBUILDER to your own application and having your application call back to ReportBuilder.APP. If you used FoxPro 2.x, this may remind you of the GENSCRNX approach.

·         Register event-handling objects in ReportBuilder.APP’s registry table. This will likely be the most popular choice because ReportBuilder.APP provides a report builder framework and lets you simply focus on handlers for report events.

Registering report event handlers

Two steps are required if you want to create your own report event handlers for ReportBuilder.APP to call when a report event fires. The first is to create a class that implements the desired behavior. It must have an Execute method that accepts an event object as a parameter, because that’s what ReportBuilder.APP expects. The second is to register the handler in ReportBuilder.APP’s handler registry table. We look at the second task first, and then spend the rest of this chapter discussing the first.

When a report event occurs, ReportBuilder.APP looks in a handler registry table to find a handler for the event. By default, it uses the handler registry table built into the APP file. This table provides handlers for many report events to have the new Xbase Report Designer dialogs used rather than the native ones. However, if you want to register your own handlers, you have to tell ReportBuilder.APP to use a different handler registry table. There are several ways to do this:

·         DO (_REPORTBUILDER) and click on the Create copy button to write the internal handler registry table to an external one. By default, this copy is named ReportBuilder.DBF. When ReportBuilder.APP starts, it looks for a table called ReportBuilder.DBF in the current directory or VFP path. If it finds such a table, it uses that table rather than the internal one. So, simply creating this copy means ReportBuilder.APP will use it without having to do anything else.

·         DO (_REPORTBUILDER) and click the Choose button to select the table to use.

·         As mentioned earlier, use DO (_REPORTBUILDER) WITH 3, <DBF to use>, to specify the desired handler registry table without any UI.

Once you specify an external handler registry table, you can manually add or edit records in that table or, in the ReportBuilder.APP Options dialog, click the Explore Registry button and edit the records in the resulting dialog.

You can tell ReportBuilder.APP to use the internal handler registry table by selecting None in the dialog that appears when you click the Choose button or with DO (_REPORTBUILDER) WITH 3, ''. Note this only affects the current session of VFP, since ReportBuilder.APP will use any ReportBuilder.DBF it finds when VFP is restarted.

The handler registry table has the structure shown in Table 4.


Table 4. The structure of the handler registry table.

Field Name

Type

Values

Description

REC_TYPE

C(1)

E, F, G, H, or X

Specifies the type of record:

H: report event handler record

F: report event filter record

X: exit handler record

G: GetExpression wrapper record

E: run-time extension editor class

HNDL_CLASS

C(35)

 

The name of the class to instantiate.

HNDL_LIB

C(50)

 

The library containing the class. This field is too short for most paths, so the library (either VCX or PRG) should be in the current directory, VFP path, or open with SET CLASSLIB.

NOTES

C(50)

 

Not used by ReportBuilder.APP.

EVENTTYPE

I

0 - 19 or -1

Report event type ID (-1 means any event). See Table 2 for a list of the values.

OBJTYPE

I

0 - 10, 99, or -1

Report object type. 99 is a special object type meaning multiple selected objects. -1 means any type of object. The rest of the values correspond with those in the OBJTYPE column in an FRX.

OBJCODE

I

0 - 26 or -1

Report object code (-1 for any code). These values correspond with those in the OBJCODE column in an FRX.

DEBUG

L

.T. or .F.

.T. to use the debug handler instead of the class specified in HNDL_CLASS.

NATIVE

L

.T. or .F.

.T. to pass the report event back to the Report Designer for native behavior.

FLTR_ORDR

C(1)

" ", "1", "2", etc.

For filters and exit handlers only, specifies the order to apply them in.

 

When a report event occurs, ReportBuilder.APP looks for the handler class to instantiate by looking for a record where EVENTTYPE matches the report event ID (or is -1), OBJTYPE matches the OBJTYPE column of the selected FRX record (or is -1), and OBJCODE matches the OBJCODE column of the selected FRX record (or is -1). Because ReportBuilder.APP uses the first handler record it finds that meets its conditions, you may need to delete or disable built-in handlers if you want to implement your own. One way you can disable a handler record without deleting it is to change EVENTTYPE to an invalid value. For example, you can add 100 to EVENTTYPE to disable a record, and then easily re-enable it by subtracting 100.

As you can see in Table 4, REC_TYPE registers different types of records. Here are the different types available:

·         The report event handler (REC_TYPE = “H”) handles a report event. This is the only type of record where the ReportBuilder.APP checks the EVENTTYPE, OBJTYPE, and OBJCODE columns.

·         The report event filter (REC_TYPE = “F”) is a class that gets an earlier crack at the report event than a handler does. While only a single handler is instantiated, ReportBuilder.APP instantiates the classes specified in all filter records, in FLTR_ORDER order, and calls their Execute methods. If any filter object’s AllowToContinue property is .F., no further processing happens and ReportBuilder.APP informs the Report Designer that the event was handled.

As you can see from these descriptions, although they can both respond to report events, there’s a big difference between event handlers and filters:

·         Filters are instantiated on every report event, while a handler is only instantiated for the event it’s registered for in the handler registry table.

·         All filters are instantiated on an event, while only a single handler is.

This means filters are good for behavior you want to occur on multiple, possibly all, events, while handlers are specific for one type of event.

·         The exit handlers (REC_TYPE = “X”) are similar to a combination of filters and event handlers. After the other processing finishes, ReportBuilder.APP runs all registered exit handlers by instantiating the appropriate classes in FLTR_ORDER order and calls their Execute methods. These handlers are really just intended to perform any post-event cleanup behavior.

·         The GetExpression wrapper (REC_TYPE = “G”) provides a wrapper or replacement for the GETEXPR dialog. It’s useful in the various places where a user clicks a button with an ellipsis (such as the one beside a field expression). There should only be one record with REC_TYPE = “G.”

·         The Run-time extension editor (REC_TYPE = “E”) replaces the dialog that displays when you click the Edit Settings button for the Run-time Extension property in the Other page of the properties dialog for an object in the Report Designer. There should only be one record with REC_TYPE = “E.”

The report event handling process

When ReportBuilder.APP is called because a report event occurred, it does the following:

·         Runs any registered filters as described earlier. Processing stops if any of them have AllowToContinue set to .F.

·         Tries to find the handler for the event using the following search priority:

REC_TYPE = “H,” EVENTTYPE = event type, OBJTYPE = OBJTYPE value for the object, OBJCODE = OBJCODE value for the object

REC_TYPE = “H,” EVENTTYPE = event type, OBJTYPE = OBJTYPE value for the object, OBJCODE = -1

REC_TYPE = “H,” EVENTTYPE = event type, OBJTYPE = -1, OBJCODE = -1

REC_TYPE = “H,” EVENTTYPE = -1, OBJTYPE = -1, OBJCODE = -1

·         If a handler is found, ReportBuilder.APP instantiates it and calls its Execute method. If a handler is not found, the event is passed back to the Report Designer and native behavior is used instead.

·         If a handler is found, ReportBuilder.APP runs all registered exit handlers as described earlier.