ReportListener Base Foundation Class (TMM)

Visual FoxPro 9.0 SP2
This is an addendum to an existing FFC class topic, covering changes in SP1 and SP2.
Back to TMM Index
PEMs SP1The following table lists public properties and methods added by this class to its parent class, ReportListener, and new in SP1.
Properties and methods Description

pageLimit Property

When set to a value > 0, represents the highest number of pages allowed in a report run (potentially across multiple reports that have been run with the NOPAGEEJECT clause).

Default -1

Remarks: The pageLimit set of properties complement the SP1 product change to handle GDI handle resources more gracefully.  Since the overall limit cannot be predicted, and varies by operating system, registry key values, as well as other programs in use on the client system, the FFC reportlisteners provides some enhanced handling of a limited set of pages from the report set. You can provide an overall pagelimit for safety, using this property, or use the accompanying properties to "fine tune" a preview of a report run before final printing. 

Although this behavior exists primarily to protect ListenerTypes 1 and 3, you can tune these values and use them for any listener and output types.

pageLimitInsideRange Property

If .T., indicates that pageTopLimit and pageTailLimit provide an inside range rather than beginning and end of report contents. Makes pageTopLimit and pageTailLimit similar to RANGE clause, but active over multiple reports with NOPAGEEJECT.

Default .F.

pageLimitQuietMode Property

Indicates whether the class provides user feedback when the report results are limited because the run exceeded the specified pageLimit safety value.

Default .F.

pageTailLimit Property

If > 0, represents the lowest number of pages for "tail" section of report run (potentially across multiple reports using NOPAGEEJECT). Use with pageTopLimit or alone. No user feedback provided.

Default .F.

pageTopLimit Property

If > 0, represents the highest number of pages for "top" section of report run (potentially across multiple reports using NOPAGEEJECT). Use with pageTailLimit or alone. No user feedback provided.

Default .F.

RemarksRemarks specific to SP1 pageLimit-related changes

All three numerics (pageLimit, pageTopLimit, and pageTailLimit) are forced to integer values and are inclusive values wherever used. All three are designed to limit native output, and have no effect when used directly on Successors. When you apply them to the lead listener, the effect applies to all listeners in the chain, because the appropriate events are not triggered and therefore are not passed by the lead listener to its Successors.

After a report run, _reportListener.getLastErrorMessage() will let you know when it happened if pageLimit was reached, but not if pageTopLimit and pageTailLimit were used. The latter is not any sort of error condition; you know what values you set. (Also, calculations should be correct in the displayed pages, the pages simply were not included in the EMFs, they were still run).

PageLimit alone will provide better performance than the more complex Top&Tail feature, when you don't need the added tuning options in Top&Tail. This is because PageLimit can use CancelReport whereas Top&Tail use IncludePageInOutput. The pageLimitQuietMode property is provided, as distinct from the native QuietMode property, for those occasions when you choose to use PageLimit for explicitly limiting previews, rather than simply to provide GDI resource safety.

You can run the report or multiple reports with ListenerType -1 for fast results as a pre-process, to decide what you want to do with these values for a current report run.

A new LOC (OUTPUTCLASS_PAGELIMIT_LOC) supports pageLimit. CHR(13)'s are replaced by spaces when this value is returned by getLastErrorMessage(). The new LOC is #DEFINED as follows:

"Your report exceeded a specified page limit (" + ;
 TRANSFORM(THIS.PageLimit) + "). " + CHR(13) + ;

Example result:

Your report exceeded a specified page limit (4).
Report execution was cancelled.
Your results are not complete. 

Effects on derived classes:

UpdateListener, providing therm and other general user feedback mechanisms in SP1, will avoid its usual "OK-Cancel" user feedback on CancelReport when pageLimit is hit. In other words, a user can't choose to continue a report when it is interrupted for this specific reason.

UpdateListener will have no awareness of top-and-tail and no feedback by default to indicate that a report has been excerpted. Users can set UpdateListener's PrintJobName, which both the listener's therm and preview's caption respect, to indicate "partial" results.

In SP2, the same stipulations for UpdateListener's behavior apply to fxTherm, which assumes the general user feedback responsibilities.

If pageLimit is reached, file-based listeners derived from utilityReportListener, such as HTML, will still provide their usual user feedback (assuming not QuietMode) at the end of a run, similar to how they would if you interrupted their run by calling CancelReport for another reason. That is, you will be reminded that your results in the file are partial unless you choose not to get the feedback (Quietmode). In this case, getLastErrorMessage() will still let you know. Like updateListener, these and other output types can use PrintJobName to show any page range behavior created by top and tail. For example, HTML shows PrintJobName in the title if you use it.

PEMs SP2The following table lists public properties and methods added by this class to its parent class, ReportListener, and new or signifanctly revised in SP2.
Properties and methods Description

commandClausesFile Property

Allows saving and restoring of the original CommandClauses.File value by any derived class that permits dynamic FRX-fileswapping during LoadReport.

Default .NULL.

Remarks See prepareFRXSwapCopy method.  These features complement the product's change to the LoadReport event.


Indicates whether the original CommandClauses.File value has been swapped for a temporary copy during a report run.

Return Values: Logical

Remarks Exposed so that FX and GFX objects can interrogate it and decide whether they may need to request a copy to be prepared for this run, if they require it and one is not already in use.

prepareFRXSwapCopy Method

Provides an FRX copy on disk, in the same path/location as the original FRX if possible to support relative file references, for use during a report run. Returns fully-qualified temporary file name it generates for the copy.

prepareFRXSwapCopyMethod( ;
    m.tcPath, m.tlKeepCopyOpen, m.tlAdjustCommandClausesInLoadReport)

Return Values: lcFile


m.tcPath provides an explicit location in which you would like the swap copy to be created. By default, it will be created in your temp files directory (using SYS(2023)) if the original report does not exist on disk and in the sample directory as your original report, if it does exist on disk. The same location as the original report is preferred, where available, to keep potential relationships of supporting files, such as image files, the same.

If you do not specifiy m.tlKeepCopyOpen as .T. and if the procedure opens the swap copy of the FRX as a table as part of creating it, the file is closed at the end of the procedure.

If you specify m.tlAdjustCommandClausesInLoadReport as .T. and if m.tlKeepCopyOpen is not .T., the CommandClauses.File member is adjusted to hold the name of the new FRX swap copy when the copy is successfully created.

removeFRXSwapCopy Method

Removes an FRX file and its matching FRT file from disk, if present.

Syntax: removeFRXSwapCopy(m.tcFile, m.tlRecycle)

Return Values: None


The m.tcFile parameter allows you to explicitly specify the name of an FRX (and its matching FRT) to be erased. This is useful for times when a reportlistener such as XMLListener needs a temporary, private copy of the FRX during a report run, as opposed to times when a swap copy, with a generated name already known to the running instance of _ReportListener, is prepared for editing during the LoadReport event.

If you specify m.tlRecycle as .T., the RECYCLE clause is used on the ERASE commands used on the FRX and FRT files. This is useful for debugging purposes when you are experimenting with FX and GFX objects.


The prepare and remove FRXSwapCopy methods are generalized forms of a facility originally provided in XMLListener so it could create metadata related to the FRX as part of VFP-RDL. With the advent of the change to LoadReport in SP2, this facility is now much more useful to other derived reportlistener classes.

It is important that this behavior be in the ancestor reportlistener class so that it is equally available to all types of reportlisteners, as well as to report-enhancing FX and GFX objects such as gfxNoRender, which can remove FRX layout controls based on your conditions for specific report runs.

These objects must request that a swap copy be made, during LoadReport, but should not create the copy themselves. If different helper objects created their own copies, no one could be sure that "their" copy of the FRX was the one loaded and executed by the Report Engine.

reportPages[1,0] Property

Holds accumulated page count info when this class runs a collection of reports as a series. Can be used in report expressions or checked after a report run (if .removeReports has not been called). Set in adjustReportPagesInfo.

Remarks:  Previous to SP2, this array property was PROTECTed and one-dimensional; it was a placeholder without meaningful information.  Please see changes to the runReports method.

runReports Method

Enhanced behavior:

In SP2, a PROTECTed hook method, adjustReportPagesInfo, runs during this method to allow derived classes to decide how to manage the reportPages array depending on their use of the reports collection. 

The _reportListener base implementation of the adjustReportPagesInfo method is useful, in that it provides a way to collect information about REPORT FORM commands that may mix object-assisted and pre-VFP-9 report engine behavior.  However, please read the comments in the adjustReportPagesInfo method carefully and override or augment this implementation as suitable for your pattern of use.

sharedListenerType Property

Provides a readwrite copy of the the Engine's ListenerType property which the Listener can share with a succession chain.  Added dynamically to any Successor that does not already have this property.

Default -1

Remarks:Please see FXListener-TMM for some remarks on the use of this property.


The following code example shows how you might use different combinations of the "pagelimit" property set.

SET PATH TO (HOME() + "samples\solution\reports")
? ox.pagelimit && -1 by default
* let's limit it:
ox.pagelimit = 2
? ox.pagelimitquietmode && we will get feedback, but we can eliminate it if preferred
* feedback indicates what occurred... 
* Now let's try the top and tail feature:
ox.pagelimit = -1
ox.pagetoplimit = 1
ox.pagetaillimit = 4
? ox.PageTotal && is 6 but we won't get 6 pages to preview
? ox.pageLimitInsideRange && is .F., so we got the "top" and "tail" portions of a report, 
                          && but we could also choose to get only the inside range.
* so let's try it again:
ox.pagetoplimit = 3
ox.pageLimitInsideRange = .T.
? ox.PageTotal && is still 6 but we got a different selection in the preview
* How do you know what values to use in these properties?
* You can find out PageTotal before you started,
* by using ListenerType = -1 to get a "fast" count:
STORE -1 TO ox.pagelimit, ox.pageTailLimit, ox.pageTopLimit
ox.QuietMode = .T
ox.ListenerType = -1
* ox.PageTotal is available to you now, so you
* know what values to use in the pagelimit properties

The following code shows you how the gfxNoRender class tests for an FRX swap copy before attempting to eliminate layout controls from the FRX being run, during LoadReport, as part of its ApplyFX implementation.

Please read the extensive comments in this method, and notice, below, that it performs this work only when it finds that it has layout controls marked for elimination in the memberdata cursor:

IF FOUND(m.toListener.MemberDataAlias)           
   * check to see if we are already working on 
   * a temporary report.
   IF (NOT m.toListener.isFRXSwapCopyPresent())
      * must perform the swap, storing
      * CommandClauses.File for later use
      m.llSwap = .T.               
   * After creating swap report or ascertaining that
   * we are already working with a temp report, 
   * delete the items in that report as required.
   * BUT FIRST make sure the swap went through as planned.
   * After an additional test,
   * if we're positive we're in the right place
   * and that we have possible memberdata content
   * we can start deleting records out of the swap copy.
See AlsoSee Also