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.
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.
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.
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..
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
|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.
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.
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.
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|
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 .
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.
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