Visual FoxPro 9 introduces an object-assisted architecture for reporting. Using this architecture and your existing report forms you can enhance the reporting process in many ways, such as:
Generating multiple output results during a single report run.
Improving printed and preview quality.
Connecting multiple report runs together to achieve one output result.
Adjusting the report content dynamically while processing data.
Generating new types of output not available previously.
This topic discusses the objects that work together to provide these enhancements, and describes what happens during an object-assisted report run.
Visual FoxPro 9's Report System is best described as object-assisted rather than object-oriented. The Report Engine is not an object. However, when you use the new architecture, the Engine delegates much of its work to objects.
|The Report Designer also does not incorporate references to Visual FoxPro classes defined in visual class libraries (.vcx files) or programs (.prg files) as layout elements. However, using the new design-time architecture, you can use instances of Visual FoxPro classes to define attributes of report layout elements. These template classes can be attached to the report definition, and dynamically provide formatting changes and method code at run time. For more information, see Report XML MemberData Extensions.|
Run-time Output Architecture
In earlier versions of Visual FoxPro the Report Engine was monolithic. It handled all processing as a single unit. When you designed a report you had the ability to attach some code at various points, primarily when the Report Engine began to process, or finished processing, a report layout band.
In Visual FoxPro 9, the Report Engine handles the movement of the record pointer through your report's data scope, and evaluates expressions. It delegates the process of rendering the results to a new Visual FoxPro baseclass, ReportListener. Because you can derive classes from ReportListener, you can affect the rendering of each individual object in a report on a much more granular level than before. For more information, see ReportListener Object.
How to Invoke Object-Assisted Reporting
You can use the OBJECT [TYPE<N>] clause on the REPORT FORM or LABEL command to tell the Report Engine what ReportListener-derived object or object type to use. You can also tell the Report Engine to use an application whose name is stored to a system variable, _REPORTOUTPUT, to nominate an appropriate object for you. If you have SET REPORTBEHAVIOR to the value
90, the Report Engine automatically requests the application whose name is stored in _REPORTOUTPUT for an object, each time you issue a REPORT FORM or LABEL command.
If you have invoked a REPORT FORM or LABEL command with the intention of providing an on-screen preview, the Report System invokes another object, the PreviewContainer, to display the preview, after they finish their jobs of evaluating and rendering the report contents. Because the PreviewContainer is a Visual FoxPro object instance, you can create previews to match a wide variety of specifications and customize them for your applications. For more information about how to create an object matching the requirements of a PreviewContainer, see The Preview Container API.
Report System Processing Modes
The ReportListener processes a report or label in two different modes, depending on the value of its ListenerType Property. You can think of these two modes as print-appropriate and preview-appropriate, or page-at-a-time and all-pages-at-once.
In both modes, your ReportListener-derived class has an opportunity to affect the rendering of each layout element, as each element in each band is processed. The modes differ in how, and when, your derived class gets access to the rendered results, or output pages, prepared by the ReportListener baseclass.
In page-at-a-time mode, the ReportListener triggers an OutputPage event as it prepares each page, at the same time as it sends each page to the printer or print queue. Your derived class has access only to the page that has just been prepared, and only at that time. This mode is efficient if you only need access to each page once. For example, you might be saving each page to disk as an image. For full information on the capabilities and options available, see OutputPage Method.
In the second, all-pages-at-once mode, the ReportListener prepares all pages for rendering and caches them. It does not trigger OutputPage events while it prepares the pages. When the report run concludes, you can invoke the OutputPage method to access any page included in the output, by page number. You can request pages in any order, multiple times.
The report is now completely prepared and ready for previewing. At this point, if you have specified a value of
1 for ReportListener's Listenertype property, the ReportListener invokes the object referenced in its PreviewContainer property to display the preview.
You can store a reference to any object fulfilling the requirements of the PreviewContainer API in this property. If it does not already have an object reference, the Reportlistener requests an appropriate object reference from an application whose name is stored in another system variable, _REPORTPREVIEW.
When the entire report is prepared, the ReportListener uses the PreviewContainer's SetReport method. This action gives the PreviewContainer a reference to the ReportListener, and indicates that the ReportListener's readiness for preview. The PreviewContainer takes control of the reporting process at this point. It uses the ReportListener's OutputPage method to request the pages required for display, as the user navigates through the report.
For more information about how to leverage the default Report Preview Application or write a replacement, see Extending Report Preview Functionality.
If the user decides to print from the preview interface, the PreviewContainer calls the ReportListener's OnPreviewClose method with appropriate instructions. The ReportListener resumes control, and prints the cache of pages it previously prepared. For more information, see OnPreviewClose Method.
This section has given you a tour of the objects that cooperate in Visual FoxPro object-assisted mode reporting. The next section discusses the events that occur during a report run, while the Report Engine and ReportListener prepare and render the report contents. It gives you the information you need to assess the report status, and write appropriate code, during different stages of preparation.
Run-time Output Processing
The following processing narrative describes the full report run. As described in the previous section, there are several ways you can ensure that a REPORT FORM or LABEL command is processed with an object reference. The different ways of invoking object-assisted reporting do not change the event sequence described in this section.
The Report Engine and ReportListener baseclass process the report in a series of events that begins just before the BeforeReport event and finishes just after the AfterReport event. The ReportListener baseclass also provides two external, or framing, events, LoadReport and UnloadReport that allow your code to pre- and post- process the report.
|For full information about ReportListener member events, refer to the various entries ReportListener Object Properties, Methods, and Events.|
Beginning the report run
When a REPORT FORM or LABEL command is processed, the Report Engine first checks to see if a previous command included the NOPAGEEJECT keyword. This keyword indicates that multiple reports are being processed together and that some output is already in the queue for eventual display or printing.
If there was no previous NOPAGEJECT, the ReportListener's OutputPageCount property value is reset to
The Report Engine next creates the ReportListener's special data session and provides its DataSessionID value to the ReportListener, using the ReportListener's FRXDataSession property. It also assigns the ReportListener's CommandClauses property; although not all CommandClauses properties are immediately available. For more information, see CommandClauses Property.
The Engine now invokes the ReportListener's LoadReport event and evaluates the return value of any user code you provide in this event. If LoadReport returns a value of False (
.F.), report processing does not continue.
|If you want to make changes to the report or label table or to the current printer driver, LoadReport is your opportunity to do so. You can also dynamically change the value of the ReportListener.CommandClauses.Prompt member and other output-target-related ReportListener.CommandClauses members.|
After LoadReport, the Engine is ready to investigate the contents of the report or label table. If specified in the table, the Engine prepares a private data session to hold the report data. It stores the DataSessionID value for the data to be processed during the report in the ReportListener's CurrentDataSession property.
The Report Engine now switches to the FRXDataSession and provides a read-only copy of the report or label table, using the alias
FRX, in this session.
|The Report Engine and ReportListener do not use this copy of the report or label definition table; in fact, they never switch to this session again during the course of the report run. The Report Engine reads the contents of the table into a separate structure for its own use.|
The Report Engine switches the data session back to the CurrentDataSession containing the data to be processed during the report run. It is now ready to begin its internal processing.
|In this section, you have learned about two data sessions used during a report run (the CurrentDataSession and FRXDataSession). These two data sessions are most critical to understand as you begin to write ReportListener code. However, other data sessions may be involved in the report run. A later section of this topic describes the relevant types of data sessions in more detail.|
Beginning the report run's internal processing
If the ReportListener is set to a ListenerType value of
1 (printing), and if you have used the PROMPT clause on the report or label processing command, or if you have set the ReportListener.CommandClauses.Prompt value to
.T. in the LoadReport event, the Print Setup dialog box is displayed at this time. The initial values in this dialog box reflect any RANGE instructions you used on the processing command; however the effective print RANGE for the report, and the values of related ReportListener.CommandClauses members, may be altered by user choices in the dialog box.
When the user closes the dialog box, the Report Engine opens the print queue, if required, unless the user canceled the print run. It also checks the current printer settings (even if no printing is requested) to determine the current page dimensions. If you used the NODIALOG keyword on the processing command but did not set the ReportListener.QuietMode property to True (
.T.), the property is automatically set to True (
.T.) for the balance of the report run. The Report Engine next calls the BeforeReport event.
|We have now reached the sequence of events that comprises the Visual FoxPro native report run. The ReportListener GetPageHeight and GetPageWidth methods have valid return values. All CommandClauses members are prepared. You can use the ReportListener's CancelReport method from now until the end of AfterReport, if you need to interrupt and prematurely end the report run.|
After BeforeReport, the Report Engine and ReportListener do their initial evaluation of various aspects of the report or label table. For example, they check to see whether you have used the system variable _PAGETOTAL in report expressions. If so, the ReportListener's TwoPassProcess property value is set to True (
.T.). The ReportListener's CurrentPass property value is also set to
0 at this time. For more information, see CurrentPass Property and TwoPassProcess Property.
You can set the TwoPassProcess property's value to True (|
Handling report content
The Report Engine and ReportListener have finished their setup phase. They now process all the band events relevant to the report. Between each band's BeforeBand and AfterBand Events, they process each layout element belonging to the band. Each layout element has the following potential events:
An EvaluateContents event, if it is a Field or Expression element.
An AdjustObjectSize event, if it is a Shape or Image element. If the Shape or Image does not fit on the page, it is possible for this event to be repeated in additional attempts on subsequent pages.
Zero to many Render events, for all types of layout elements. An element does not trigger a Render event if its Print When expression evaluates to True (
.T.). Otherwise, it triggers at least one Render event and potentially more, if it spans bands and pages.
If the ReportListener.TwoPassProcess property is True (
.T.), the Report Engine and ReportListener run through all the band and element events twice. The first time, they evaluate expressions and placement on output pages without actually rendering anything. This is a pre-processing, or calculation pass. It allows the Report Engine to calculate a _PAGETOTAL value for use in rendering output on the second, or rendering, pass. The pre-processing pass also provides an opportunity for your code to calculate other values.
|You can use the ReportListener Debug Foundation Class to examine the report run event processing closely. Use the class's Verbose property to examine the contents of the ReportListener's CommandClauses member at different points in the result, and receive extended information about various methods' object parameters.|
Ending the report run
The final report band processed is either a Page Footer band or a Summary band; even if you use the ReportListener's CancelReport method, the Report System finishes processing a full page before interrupting the report.
After the final report band processes, the ReportListener and Report Engine begin clean-up procedures.
The report's private data session, if any, is closed before the AfterReport event occurs. To ensure that the ReportListener always has a valid data session to which it can return, if the report used a private data session, the ReportListener's CurrentDataSession is reset to
1, the default value, at this time. If the report did not have a private data session, the CurrentDataSession is outside the Report System's control, and does not change.
The ReportListener's AfterReport event is invoked next. Following AfterReport, natively-provided output is finalized. If a print stream was opened for this output, it is closed now. At this time, the CancelReport method ceases to have any effect on internal processing; there is no more "report run" from the Report Engine's point of view.
The ReportListener.QuietMode property value is restored to False (
.F.), if it was turned out temporarily as a result of the NODIALOG keyword.
If the ReportListener.ListenerType property value is 1, the ReportListener invokes the PreviewContainer to display the report output at this point, followed by UnloadReport after the PreviewContainer returns control to the ReportListener. If the ListenerType property value is not
1, the ReportListener's UnloadReport method occurs immediately.
After the UnloadReport event, the Engine closes the special data session in which it opened a copy of the report or label table. It resets the ReportListener.FRXDataSession property to its default value,
-1, at this time, signifying that there is no valid session fulfilling this purpose between report runs.
The ReportListener.TwoPassProcess is reset to its default value of
.F.. By putting this property back into its default state, the ReportListener makes it possible for you to explicitly re-assign this value before the next time you use the ReportListener instance. The ReportListener.CurrentPass property value is not reset; its current value gives you a way to assess what happened on the previous report run.
Unless you used the NOPAGEEJECT keyword on this report run, the ReportListener GetPageWidth and GetPageHeight methods stop proving useful values at this point.
Data Sessions Associated with Object-Assisted Report Runs
When each ReportListener event begins, the data session is the one in which the ReportListener object was originally created. This data session is not always the same data session in which you issued the REPORT FORM or LABEL command. The ReportListener Base Foundation Class uses the term ListenerDataSession, with an associated protected member property, to describe and manipulate the data session in which it originated.
In the framing events of the report run (LoadReport and UnloadReport), you should return to the ListenerDataSession upon completing any code sequence that switches data session. The Report Engine will expect to find the ReportListener object in this state at the conclusion of these events. Refer to SET DATASESSION Command for more information about switching between data sessions.
As events progress through the other report events and the Report Engine's internal processing, your ReportListener code normally switches between the data sessions indicated by the ReportListener's CurrentDataSession Property and FRXDataSession Property. Switching between these two data sessions allows you to reference the report's data files as well as the copy of the report or label table as you generate output, as needed.
The Report Engine also provides you with information about a fourth data session: the data session in which you issued the REPORT FORM or LABEL command. You find this DataSessionID value as a StartDataSession member property of the CommandClauses Property object. You do not normally need to reference this fourth data session in ReportListener code. It is provided in case you need to reference the environment in which the command occurred. For example, you might want to check the setting of a session-scoped environmental setting such as SET("DELETED"). For more information, see Customizing the Environment of a Data Session.
The REPORT FORM or LABEL command's StartDataSession may be the same data session as the one referenced by the ReportListener's CurrentDataSession property. However, if the report table specifies a private data session for the report run, the CurrentDataSession and StartDataSession are not the same. The StartDataSession may also be the session in which the ReportListener was created, if your ReportListener object is scoped to a form or session object. However, a ReportListener is often global in scope, for example created by the Report Output Application or by a manager object belonging to your application. In this case, the StartDataSession and ListenerDataSession are unrelated.