ReportListener Decorator Foundation Class (TMM)

 
Visual FoxPro 9.0 SP2
This page is an addendum to an existing FFC class topic, covering a class introduced in SP2. The remarks in this docoid are intended to improve, and in some cases to correct, the VFP SP2 CHM entry.

This essay also offers a centralized place to offer some general perspective on the VFP 9 Reporting System Extensions design, and the place of this class within the design.
Back to TMM Index
GeneralAbout this class
FXListener is a host class for the FX reporting subsystem. It provides a place for you to put extension-producing helper objects.

When designing report output extensions, it is important to distinguish between your opportunities to embellish multiple types of output with new content from your ability to create new output types using reportlisteners. 

New output types are typically created by deriving classes from the base class ReportListener, since only a ReportListener-derived object can receive messages from the VFP Report Engine.

Decorations of report content, on the other hand, generally do not have to be implemented directly in a reportlistener object and in general should not be. Attaching this type of code directly to a reportlistener limits your ability to share the decoration with multiple forms of output.

Caution note Caution
This recommendation remains in the SP2 CHM version of the Considerations for Creating New Report Output Types topic, http://msdn2.microsoft.com/en-us/library/ms977622(VS.80).aspx. However, the original illustrative code for FXListener and all connection to  FXListener and the FX reporting subsystem, as written in the RTM version of the same topic, have been inexplicably removed. 

A link to the Report XML MemberData Extensions topic remains, but that link is described as "an example of object-assisted reporting that uses additional cursors". While it certainly is an example of using additional cursors, the original connection between these topics was the illustration of the technique using an FX implementation, rather than assigning the code to a ReportListener object.

FXListener's  superclass, _ReportListener, implements a Chain of Responsibility pattern using Successors.  Beginning in RTM, this feature has leveraged the base reportlistener class to add extension output types, by providing a way to create multiple output types during a single report run. Each Successor in the chain receives messages from the "lead" reportlistener, which is in communication with the Report Engine.  While only the "lead" listener can get native VFP report output (as determined by its ListenerType property), Successors can each drive an additional output result using Xbase code. 

Tip note Tip
If a Successor supports multiple types of output, it refers to its OutputType property to know what form of output has been requested on this run.  If it needs to know what behavior is being used by the Report Engine to create native output, perhaps to distinguish between all-pages-at-once (preview) and page-at-a-time (print) behavior, it refers to its sharedListenerType property. 

A Successor can also refer to its own ListenerType property to make some determinations, on the understanding that, when a reportlistener is serving as a Successor, this property doesn't invoke any native behavior.

In SP2, FXListener introduces Decorator-Visitor patterns to the FFC ReportListener feature set.  Its two collections, FXs and GFXs, provide opportunities for small and light extension objects to contribute behavior  and "special effects" to any output type, and to multiple outputs that might be attached to a report run in a Successor chain. 

Note Note
The fact that decoration features are better implemented in small custom classes of any type, rather than the new-in-VFP 9 ReportListener classes, is one significant reason why Successors were implemented in RTM and the FX subsystem was only sketched into the RTM documentation.

Another significant reason was performance. Many decoration effects require code in the two dynamic methods (EvaluateContents and AdjustObjectSize), and in RTM there was no efficient way to turn these methods off if they contained any code in the Xbase class heirarchy. Please refer to the CallEvaluateContents and CallAdjustObjectSize property topics, new in SP2, for more information. 

Each reportlistener that receives the effect renders it as appropriate to its output type, if it is relevant. For example, you can use fxRotate to apply rotation to layout controls in a report . Preview and Print output show the rotated effect, and many extension output types could conceivable share it. However, HTMLListener does not show the effect, because, today, browsers have limited ability to render elements at an angle.  If, in the future, browsers have improved ability to show rotation, HTMLListener could apply a CSS style instruction for this purpose. A DataListener that you might create to use REPORT FORMs as an application-to-application data mapping device, on the other hand, would ignore the effect completely.

FXListener provides two collections to allow for two basic types of effects that will be processed in a known sequence. Although this basic sequence is fixed, within each collection there is no guarantee of order, so extensions should be mindful of possible side effects.  They should not rely on inter-effect communication.

  • First, the FXs collection is processed. Objects in this collection are intended to decorate or alter the base output being provided by the report engine. For example, fxMemberDataScript might alter the contents of a text expression or the size of a rectangle.
  • After this work is done, the GFXs collection is processed to add  new content, which can include drawing to the report page directly using GDIPlus methods.  Because this work occurs after the base content has been altered by the FXs collection, GFX objects can evaluate the current base output appropriately before deciding what they want to add..
Tip note Tip
The PROTECTED method sendFX in which FXListener invokes the ApplyFX method for each collection member, sends a reference to the listener, a method token indicating what report event is occuring, and all parameters for that event.

Each ApplyFX invocation receives all event parameters passed by reference, which means that each object has a chance to change the contents of each parameter.  For example, custom objects can change the contentsToBeRendered (tP7) value in the Render event for text and image objects.

You can see an example of this behavior in the topic for the gfxOutputClip class.

The Render method is critical to the actual display of any layout element. If any GFXs are loaded, FXListener loads its FFCGraphics member object with a GDIPlus handle to the current page before processing the GFXs during the Render event, so that individual GFX objects don't have to create their own.   Note that it loads a reference to an instance of the gpGraphics class from the _GDIPLUS.VCX library into its FFCGraphics member object by default.  You can supply a reference to any gpGraphics-derived class that you prefer.

FXListener also pays attention to the return value of the ApplyFX method, as it iterates through the GFXs collection, during the Render method; this is the only event for which the return value of the ApplyFX method is significant.   Because there is no guarantee of collection order, by convention the highest value returned by any GFX object in the set is applied. (This strategy is repeated in the approach used by FX and GFX objects in deciding what value to assign to CallEvaluateContents and CallAdjustObjectSize; when participants in the report run have an opportunity to "cast a vote" on invocation of a dynamic method, they should only move the value up, not down, lest they impair some other object's ability to work.)

The allowable values are #DEFINEd in REPORTLISTENERS.H, as follows:

#DEFINE OUTPUTFX_BASERENDER_AFTERRESTORE 0 
#DEFINE OUTPUTFX_BASERENDER_RENDER_BEFORE_RESTORE 1 
#DEFINE OUTPUTFX_BASERENDER_NORENDER 2 
#DEFINE OUTPUTFX_BASERENDER_RENDERXBASEONLY 3 

*&* The following two values may not have any 
*&* practical use, because Xbase listeners may not 
*&* have any practical way of making this distinction, 
*&* so the previous value should be used for now. 
*&* They are designated here for completeness: 
#DEFINE OUTPUTFX_BASERENDER_RENDERXBASEONLY_AFTER 4 
#DEFINE OUTPUTFX_BASERENDER_RENDERXBASEONLY_BEFORE 5 

#DEFINE OUTPUTFX_DEFAULT_RENDER_BEHAVIOR OUTPUTFX_BASERENDER_AFTERRESTORE             
            

Tip note Tip
For a complete walkthrough of the design and implementation of a GFX object, please refer to the whitepaper Data Visualization in Reports with VFP 9.0 SP2.

FXListener does more than providing the necessary framework for the FX subsystem; it also implements some concrete examples of FX subsystem functionality.

While the SP2 CHM entry indicates that you will not instantiate this class directly, there are extremely good reasons to do so.  In fact, every time you SET REPORTBEHAVIOR 90  and instantiate a print or preview listener, the SP2 Report Output Application provides an instance of FXListener. This ensures availability of extension behavior, regardless of the base or extension output type you need for a report run.

Because the Report Output Application makes FXListener responsible for in-the-box implementation of print and preview behavior in SP2, FXListener undertakes to guarantee the availability certain FX and GFX implementations required for in-the-box behavior.  It unilaterally adds them to its collections if they are required for a report run. These implementations are:

  • a Feedback FX object
  • a MemberData Script processing FX object
  • a Rotate processing GFX object
  • a Render-suppression GFX object

While it undertakes to guarantee these features,  and while there are  default implementations of each in its class library, FXListener does not "own" the implementations.  It provides matching Class, ClassLib, and Module properties to allow you to specify the implementations you prefer. 
Tip note Tip
There are slight differences in the support for each of these "guaranteed" helper objects. You can opt to remove support for Render-suppression by setting the gfxNoRenderClass property to an empty string.  You cannot do the same thing for the other three guaranteed object types, because these capabilities are required for many in-the-box features that would be available to users if you deployed the default ReportBuilder with your application. If you try to set their Class properties to a null string the value of the property will not change. 

Because gfxNoRender is exposed only as a pair of Advanced Properties, you can remove this feature from ReportBuilder's registry table very easily and it is safe for FXListener to consider it a non-required helper object as well.  See Data Visualization in Reports with VFP 9.0 SP2 for details.

Even though the Feedback facility has a guaranteed default Class value, it isn't necessarily loaded all the time. FXListener checks its QuietMode property value and its CommandClauses.NoDialog member value first.  It also does not unilaterally load the Feedback object if it is functioning as a successor.

The MemberData Script and Rotate objects are unilaterally loaded only when memberdata indicates that their presence is required for a report run.  However, once loaded, they may stay loaded for multiple reports; they don't have to be re-instantiated for each run.


The Feedback FX object, fxTherm, provides the means for FXListener to take over the responsibilities of UpdateListener for user feedback during report processing.  FxTherm's code is almost entirely extracted from UpdateListener's code (as emended and improved for SP1 and SP2). Its development served as proof of concept that almost any type of report decoration, even one innately tied to many reporting events, as UpdateListener's display is, could be easily ported into an FX or GFX object rather than handled directly in a reportlistener-derived class.  It also serves to show how an object derived from any baseclass (in this case, a form) can implement the FX API  and participate in the FX reporting subsystem.

Along with its FFCGraphics member FXListener also offers some other common behavior to be shared across multiple effects:
  • an instance of FRXCursor available to all,
  • an open Memberdata cursor, which it requests from FRXCursor, and an exposed memberDataAlias property, from which all objects can extract whatever extension intelligence they require, 
  • a runCollectorResetLevel property which, while not used by FXListener itself, exposes the new runCollector feature  to be  shared by all run participants. 
Tip note Tip
The runCollector feature provides a way for reportlisteners to accumulate extension information during the course of a report run, so the accumulated data  can be used at the end of the run. The protected runCollector property is implemented at the _ReportListener level as a NULL. By design, the member can be a cursor alias, a collection object, an empty  or any other scheme you prefer.

XMLListener implements runCollector as a collection object by default. However,  when it reads and writes to runCollector it respects the shared repository's type; it is able to its add contents to a cursor or empty object. 

An example of the use of the shared runCollectorResetLevel property, as implemented by XMLListener, is included below.

PEMs commentsProperties and Methods

The following table lists public properties and methods added by this class to its parent class, _ReportListener, and requiring some additional comment to what appears in the VFP  SP2 CHM.

Properties and methods Description

frxCursor Property

This property and related features, including the loadFrxCursor Property,  are now provided by  FXListener rather than UtilityReportListener, to ensure  frxCursor assistance in rendering all types of output, rather than only the file-based types for which UtilityReportListener is primarily responsible . Please see important notes on expanded functionality available  in frxCursor for SP2 .

getPathForExternals Method

This method, previously provided by UtilityReportListener, can be used by your extensions to ensure a unified approach to finding "bits", such as class libraries you wish to load.  It was originally conceived as a way to indicate where UtilityReportListener would create its configuration table on disk, if this activity occurred dynamically at runtime,  and it is designed for override to fit any deployment  system of your own.

reportStartRunDateTime and reportStopRunDateTime Properties

Please refer to the TMM docoid on fxTherm, the FX User Feedback Implementation class, for additional information regarding these properties.

ExampleExamples

Please refer to   the whitepaper Data Visualization in Reports with VFP 9.0 SP2 for appropriate examples of FXListener's public methods, such as AddCollectionMember and RemoveCollectionMember, instructions on how to make FX and GFX objects available to the default reportlisteners, etc. The Examples section of the TMM docoid on the GFXExample class also has some notes about using AddCollectionMember and RemoveCollectionMember.

The following code excerpt shows how XMLListener checks the runCollectorResetLevel property at the conclusion of a report run, to decide whether it is time to reset the contents of the repository.  This evaluation takes into consideration whether the document being prepared is part of a chained report run that is not yet complete.

[excerpt from PROCEDURE XmlListener.AfterReport]
IF OUTPUTXML_CONTINUATION IF THIS.runCollectorResetLevel = OUTPUTFX_RUNCOLLECTOR_RESET_ONREPORT THIS.resetRunCollector() ENDIF THIS.ResetReport() ELSE IF THIS.runCollectorResetLevel > OUTPUTFX_RUNCOLLECTOR_RESET_NEVER THIS.resetRunCollector() ENDIF THIS.ResetDocument() * .. more code here ENDIF

See AlsoSee Also