Visual FoxPro 9's object-assisted reporting mode offers almost unlimited potential for expanding your output horizons using ReportListeners. The Report Output Application offers a convenient mechanism to help you to share your reporting extensions with other users.
This topic provides some guidelines to consider when developing and distributing Report System extension objects.
Design Decisions for Reporting Extensions
- Determine whether your goal is to provide a new type of output result not available from baseclass behavior, such as Rich Text, or to enhance multiple output formats, in an output-result-independent manner.
If your goal is to enhance existing output results, your object probably does not need to be a ReportListener, and it is not an Output Type.
Design this object to be supportable, as a member or helper object, by multiple ReportListeners. It needs to be single-purpose and small, for efficiency. Design a ReportListener to accept enhancements by iterating through a collection of these objects.
By contrast, if your goal is a new type of output result, it must be a ReportListener-derived object, so it can partner successfully with the Report Engine. It is a good candidate for a chain of responsibility mechanism, as implemented by the ReportListener Base Foundation Class. This mechanism allows you to generate multiple types of output during the same report run.
Note: You can use both techniques together. Register your enhancement objects to a single ReportListener, and apply their effects to the output before sending events to Successors. Each Successor can decide which enhancements are applicable to its type of output. An example is included in this topic.
- Determine your error handling strategy, and publish your object's requirements in this respect.
- ReportListeners partner with the Report Engine in a unique way, because of the way they process user code during a single Visual FoxPro command. They are also potentially partnered with other user code in DataEnvironment methods and user-defined functions (UDFs) embedded in the report or label file. There are different ways to handle REPORT FORM commands that do not complete properly. Review Handling Errors During Report Runs for information and suggestions. When you decide on a strategy, make sure the users of your ReportListener class have proper information to provide proper cleanup when errors occur.
- Place ReportListener-required temporary tables in the special session provided for this purpose.
The ReportListener object's FRXDataSession Property provides you with the information you need to access the special copy of the report definition file (.frx) during the report run. If your ReportListener needs to do work with additional cursors, use this protected session, rather than the data session containing the data on which the report is based, represented by the ReportListener object's CurrentDataSession Property. This strategy ensures that you leave the user's "real" data session undisturbed, and also ensures cleanup of these temporary files at the conclusion of a report run.
Caution: Be sure to switch appropriately between data sessions during the report run, when you want to use the report copy or your other workfiles. For more information, see SET DATASESSION Command. Tip: For an example of objected-assisted reporting that uses additional cursors in FRXDataSession, see Report XML MemberData Extensions.
- Make a distinction between the ReportListener's OutputType Property (Visual FoxPro) and ListenerType Property in your design.
- OutputType gives you the value with which the Report Output Application was invoked, while ListenerType represents the native output-generation mode provided by the baseclass ReportListener. A ReportListener-derived class can be registered to handle many different OutputType values in the Report Output Application's registry table, just as the baseclass ReportListener supports different ListenerType values. However, one OutputType can also support different baseclass behavior and native output, by supporting multiple ListenerType values.
Example: Applying Multiple Effects to Multiple Output Types
This example creates a ReportListener-derived class, FXListener, that leverages the ReportListener Base Foundation Class's ability to handle a Successor chain. This class pre-processes all ReportListener events with effects-handling before invoking the Successor chain. All Successors in the chain receive the benefits of the effects collection, and process the results of all applied effects to whatever extent they are capable of using the changes.
FXListener supports a collection of effect objects. FXListener requires that all effect objects are instances of classes derived from any Visual FoxPro base class, and implementing the following simple interface:
DEFINE CLASS FX As Custom && any base class * required interface: PROCEDURE ApplyFX(toListener, tcProgram,; tP1, tP2, tP3, tP4, tP5, tP6,; tP7, tP8, tP9, tP10, tP11, tP12) * FXListener passes a reference to itself as toListener, * PROGRAM() as tcProgram, and * all ReportListener parameters as tP1 through tP12, * for each ReportListener event during the run. * Parameters tP1 through tP12 are passed by reference, * so that changes made by effect objects are visible * to FXListener and its successors upon return from * this method. ENDPROC ENDDEFINE
You register one or more effects for a report run by adding an instance of one or more effect-handling classes to a FXListener-derived object's effects collection. You then invoke REPORT FORM commands with the OBJECT clause referencing your FXListener object, to receive the results of your effects.
The example below includes the full code for the FXListener class and a simple effect class. The effect class applies the UPPER() function to all text output. The example adds an instance of this effect class to FXListener's collection. It also designates two Successors to FXListener (an instance of the ReportListener User Feedback Foundation Class and an instance of the ReportListener HTML Foundation Class) before it proceeds to run a report.
The effect object's change to report output is reflected in the preview output displayed by the primary ReportListener (the instance of FXListener) as well as the HTML output generated by the HTMListener Successor object in its chain. Running at the same time, the user feedback provided by the ReportListener User Feedback Class incorporates the contents of the primary ReportListener's customized PrintJobName Property.
|The text output is changed to upper case in Render rather than the EvaluateContents event, for better performance. Using Render also provides the ability to change the output of Label layout elements as well as Field or Expression layout elements in the report; EvaluateContents events do not occur for Label layout elements.|
If you add an effect object that generates changes to font attributes, rather than text contents, using the EvaluateContents event, you see results of these changes in the preview. However, you do not see the same results in the HTML; the ReportListener HTML Foundation Class takes its font attributes from the layout elements' definitions in the FRX, and does not incorporate dynamic changes made at run time. Well-designed effect classes are unaware of the capabilities, requirements, and limitations of various ReportListener-generated types of output.
For other examples of effect classes, see Report XML MemberData Extensions.
#DEFINE FFC_HOME HOME() + "FFC\" LOCAL loPrimaryRL, loSuccessorRL, loSuccessorRL2 loPrimaryRL = CREATEOBJECT("FXListener") loSuccessorRL = NEWOBJECT("UpdateListener", ; FFC_HOME + "_ReportListener.vcx") loSuccessorRL2 = NEWOBJECT("HTMLListener", ; FFC_HOME + "_ReportListener.vcx") WITH loPrimaryRL .PrintJobName = "FX Listener Test" .QuietMode = .T. .ListenerType = 1 .FXs.Add(CREATEOBJECT("FXSimple")) .Successor = loSuccessorRL ENDWITH loSuccessorRL.Successor = loSuccessorRL2 loSuccessorRL2.QuietMode = .T. REPORT FORM ? OBJECT loPrimaryRL * FXListener, * Effects-applying ReportListener-derived class: DEFINE CLASS FXListener AS _ReportListener OF ; (FFC_HOME + "_reportListener.vcx") FXs = NULL PROCEDURE Init() THIS.FXs = CREATEOBJECT("Collection") ENDPROC PROCEDURE SendFX(tcProgram, ; tP1, tP2, tP3, tP4, tP5, tP6, tP7, tP8, tP9, tP10, tP11,tP12) IF (NOT THIS.IsSuccessor) AND THIS.FXs.Count > 0 * Only the lead does this work. * The order of the * invocation of this method, * which precedes the DODEFAULT() * in each event, * makes the results available * for all Successors. LOCAL loFX FOR EACH loFX IN THIS.FXs FOXOBJECT loFX.ApplyFX(THIS,tcProgram, ; @tP1, @tP2, @tP3, @tP4, @tP5, @tP6, ; @tP7, @tP8, @tP9, @tP10, @tP11, @tP12) NEXT ENDIF ENDPROC PROCEDURE BeforeReport() THIS.SendFX(PROGRAM()) NODEFAULT RETURN DODEFAULT() ENDPROC PROCEDURE AfterReport() THIS.SendFX(PROGRAM()) NODEFAULT RETURN DODEFAULT() ENDPROC PROCEDURE BeforeBand(nBandObjCode, nFRXRecNo) THIS.SendFX(PROGRAM(),nBandObjCode, nFRXRecNo) NODEFAULT RETURN DODEFAULT(nBandObjCode, nFRXRecNo) ENDPROC PROCEDURE AfterBand(nBandObjCode, nFRXRecNo) THIS.SendFX(PROGRAM(),nBandObjCode, nFRXRecNo) NODEFAULT RETURN DODEFAULT(nBandObjCode, nFRXRecNo) ENDPROC PROCEDURE EvaluateContents(nFRXRecno, oObjProperties) THIS.SendFX(PROGRAM(),nFRXRecno, oObjProperties) NODEFAULT IF (NOT ISNULL(THIS.Successor)) THIS.SetSuccessorDynamicProperties() THIS.Successor.EvaluateContents(nFRXRecno, oObjProperties) ENDIF DODEFAULT(nFRXRecno, oObjProperties) ENDPROC PROCEDURE AdjustObjectSize(nFRXRecno, oObjProperties) THIS.SendFX(PROGRAM(),nFRXRecno, oObjProperties) NODEFAULT IF (NOT ISNULL(THIS.Successor)) THIS.SetSuccessorDynamicProperties() THIS.Successor.AdjustObjectSize(nFRXRecno, oObjProperties) ENDIF DODEFAULT(nFRXRecno, oObjProperties) ENDPROC PROCEDURE Render(nFRXRecNo,; nLeft,nTop,nWidth,nHeight,; nObjectContinuationType, ; cContentsToBeRendered, GDIPlusImage) THIS.SendFX(PROGRAM(),nFRXRecNo,; @nLeft,@nTop,@nWidth,@nHeight,; @nObjectContinuationType, ; @cContentsToBeRendered, @GDIPlusImage) NODEFAULT RETURN DODEFAULT(nFRXRecNo,; nLeft,nTop,nWidth,nHeight,; nObjectContinuationType, ; cContentsToBeRendered, GDIPlusImage) ENDPROC ENDDEFINE * example FX class fulfilling FX design contract * by implementing required interface: DEFINE CLASS FXSimple AS Custom PROCEDURE ApplyFX(toListener, tcProgram,; tP1, tP2, tP3, tP4, tP5, tP6, ; tP7, tP8, tP9, tP10, tP11, tP12) IF ATC("Render",tcProgram) > 0 tP7 = UPPER(tP7) * cContentsToBeRendered * is Render's 7th parameter * Note that many changes to * cContentsToBeRendered in the Render method * will require use of STRCONV() to * provide a Unicode result, although this * simple change does not require it. ENDIF ENDPROC ENDDEFINE