VFP 9.0 Reporting System Fundamentals - Part 2
By Colin Nicholls
This article continues from Part One.
2. The Reporting System at Run TimeIn this section, we are going to cover how the object-assisted reporting engine works during a report run. Recap: Reporting in VFP 8.0 and earlierThe Reporting System in Visual FoxPro 8.0 and earlier was a bit of a black box. We had about three choices of places to send the report output, using clauses on the REPORT FORM command:
There are a few other clauses, PROMPT, ASCII, but basically that was it. ![]() Reporting in VFP 9.0By default, the reporting system in VFP 9.0 processes reporting command syntax in exactly the same way as in previous versions of Visual FoxPro. The same REPORT FORM commands will produce identical results as VFP 8.0 and earlier. ...Of course, that's not the end of the story. The report engine in VFP 9.0 has been enhanced significantly:
OK, so how do we get to the new engine?If the existing REPORT FORM syntax produces the same results as previous versions of FoxPro, then how do we get to the new features of the report engine? The answer is: Through a new Visual FoxPro base class, and some new command syntax. The Reportlistener Base ClassIf we take a look at the Help file topic for the ReportListener class, we can see a daunting array of properties and methods: LoadReport, BeforeBand, AdjustObjectSize, GetPageHeight, Render, OutputPage, gdiPlusGraphics... these look like the kinds of things that a reporting engine would need to implement. In fact, that's exactly what they are.
Properties and MethodsLet's take a look at one: oX = NEWOBJECT("Reportlistener") The report engine, formerly a "black box", has been extended and re-engineered to expose a good deal of its functionality as a FoxPro base class that we can subclass and attach our own custom behavior to, giving us a kind of an object-oriented report run. New Syntax: REPORT FORM .. OBJECT xThe object-assisted mode of the report engine is enabled through some new command syntax:
oX = NEWOBJECT("Reportlistener") This plugs an instance of the Reportlistener class in to the report run:
In this way, the new GDI+ report engine is enabled; custom report previews can be displayed, and almost unlimited output types are possible, depending on what variety of report listener class we use.
Properties of the ReportListener classLet's look at some of the properties of a ReportListener in more detail: The listenerType propertyListenerType controls how rendered report pages are treated by the reporting engine:
The OutputPage() methodAccording to the help file, OutputPage "Provides access to the current page or the full range of pages for a report run, according to the value of the ListenerType property." Is OutputPage() an event or a method? It depends on how the ListenerType property is configured. "Page at a time" mode (event style):
When listenerType is 0 or 2, the report engine sends the current page number after it has rendered a page. If we wished, we could write code in OutputPage() using DODEFAULT() to echo the page to an alternate output device. When listenerType is 0, the report engine will pass the GDI+ graphics handle to the current printer in this parameter. When listenerType is 2, this parameter will have the value 0. "Spool and wait" mode (method style):
When listenerType is 1 or 3, we can specify which page number we wish to direct to an output device using this parameter. When listenerType is 1 or 3, we can invoke OutputPage() using this parameter to specify the destination device, indicating the type of device in the third parameter (iType). Aside: OutputPage() also has additional parameters: 4 dimension parameters and 4 clip parameters. These are useful for advanced techniques such as described in my article, Techniques for an alternative Report Preview UI. We'll look at examples of using OutputPage() in more detail later on. The previewContainer propertyThis property is only used when listenerType = 1. The report listener expects it to contain an object reference to a FoxPro form class or similar. We'll talk more about this property later on. What happens during a report runDuring an object-assisted report run:
SET REPORTBEHAVIOR 80 | 90Now that we have learned how to run reports in object-assisted mode, using the new reportlistener class and the new REPORT FORM.. OBJECT syntax, let's revisit a statement we made earlier: "The reporting system in VFP 9.0 processes reporting command syntax in exactly the same way as in previous versions of Visual FoxPro. The same REPORT FORM commands will produce identical results as VFP 8.0 and earlier." There is a new SET command in Visual FoxPro 9.0 that affects the validity of that statement: SET REPORTBHAVIOR. According to the help file, this command "Configures how Visual FoxPro processes REPORT FORM and LABEL FORM commands." By default, REPORTBEHAVIOR is set to 80 to ensure backward compatibility with existing applications that may assume that the reporting system and preview windows work a certain way. You can still use the new REPORT FORM .. OBJECT syntax to process reports in object-assisted mode, if you wish. Observe: SET REPORTBEHAVIOR 80 Old-style: REPORT FORM _samples + "\Solution\Reports\colors.frx" PREVIEW New Style: ox = NEWOBJECT("Reportlistener") But when REPORTBEHAVIOR is set to 90, the report engine will use new-style object-assisted mode when processing normal REPORT FORM syntax. It can't process reports in object-assisted mode unless it has a reportlistener object. How does it get one when there is no OBJECT clause to give it one?
This brings us to another new system variable: _REPORTOUTPUT. System Variable: _REPORTOUTPUTFrom VFP9 Help file: "_REPORTOUTPUT Specifies the Visual FoxPro handler application providing ReportListener derived classes to be used with the REPORT FORM command, maintaining a registry of ReportListener derived classes for different output results." Similar to _REPORTBUILDER, _REPORTOUTPUT specifies a program or application. The task of this application, however, is to instantiate and manage instances of the reportlistener class to be provided to the report engine in support of REPORTBEHAVIOR 90. _REPORTOUTPUT specifies the Reportlistener Factory application. The program must accept two parameters:
The outputType propertyAn additional clause to the contract between report engine and listener factory is that the report listener handed back to the factory must have its OutputType property set to the same value as the type of output requested (i.e. the value of the first parameter). outputType vs. listenerTypeThe report engine passes the requested output type to the listener factory program, but otherwise ignores this value. It is up to the listener object to decide how to interpret the output type, and set the listenerType property appropriately. As we saw earlier, the listenerType property will in turn affect how the report engine interacts with the report listener object during the report run. A derived reportlistener class may choose to:
Example: A simple listener factoryConsider the following program, RL_FACTORY.PRG:
LPARAMETERS outputRequested, listenerRef Note that this program returns a reportlistener with a listenerType of 0, irrespective of what output type the report engine requests. Now, in the Command window, we can test this out:
SET REPORTBEHAVIOR 90 Even though our code thinks it is asking for a report preview, the report is actually sent to the printer - because our factory program returned a base reportlistener class with a listenerType of 0. Clearly, in practice, we'll need more sophisticated report listeners, and a more capable report listener factory. One like, say, ReportOutput.App. Introducing ReportOutput.AppThe default listener factory is provided in the form of an application, ReportOutput.App. In addition to the requirements of a listener factory described above, ReportOutput.App also provides the following:
ReportOutput.App's reportlistener cacheSo we know that ReportOutput.App is going to create and return references to report listener classes. What is not so obvious is that it caches these instances in a PUBLIC collection. If our code performs two REPORT FORM TO PRINT commands in succession, the second command will re-use the listener instance created for the first command. The name of this public collection is _oReportOutput. There is one listener instance for each requested output type, the collection key being the string representation of the output type for which the listener was created. Let's see this in action. First, we initialize the environment:
CLEAR ALL Then we tell ReportOutput.App to initialize its collection with a "printing" listener, and configure the instance:
DO (_reportoutput) WITH 0 And now, print a report: REPORT FORM customer.frx TO PRINT We can interrogate the listener for interesting information:
? _oReportListener['0'].PageTotal When we print another report, the report engine will receive and use the same listener instance: REPORT FORM fileexpr.frx TO PRINT We should have seen the same print job name in the progress bar and it should also appear in the list of pending documents in the operating system's Printer Queue. Do we need a ReportOutput.App?The Visual FoxPro 9.0 report engine requires ReportOutput.App (or a suitable replacement) in order to support SET REPORTBEHAVIOR 90 and the familiar REPORT FORM commands. Our applications may not need it, if we use the REPORT FORM ... OBJECT syntax for our report run commands instead. Remember, though, that if we wish to use the NOPAGEEJECT clause to chain report runs into a single print job, we will need to do something similar to what ReportOutput.App does with respect to caching and supplying a common reportlistener reference across multiple report runs. A listener for every output: /FFC/_reportlistener.vcxReportOutput.App doesn't just serve up base reportlistener classes. It creates instances of sophisticated subclasses derived from Reportlistener, the same foundation classes that we can find in the ./FFC/_reportlistener.vcx class library. These classes are documented in the VFP9 Help file topic ReportListener Foundation Classes.
These classes include built-in support for many features not available with a "vanilla" reportlistener:
In examples to come, we will see how to use some of these listener classes in detail. Aside: _reportlistener.vcx has changed in Service Pack 2. UpdateListener is deprecated, because the progress bar and user feedback is implemented differently, and is now common to all FFC listener classes derived from a common ancestor class, FxListener. How ReportOutput.App maps "output type" to "listener class"ReportOutput.App has an internal table that maps the requested output type (integer value) to a specified reportlistener class from the FFC/_reportlistener.vcx class library:
For more information on ReportOutput.AppSee the following help topic in the Visual FoxPro 9.0 help file:
Understanding the Report Output application New Syntax: REPORT FORM .. OBJECT TYPENow that we understand the role that _REPORTOUTPUT and ReportOutput.App play in the report engine's support of report command syntax, it is a good time to consider some more new syntax: REPORT FORM .. OBJECT TYPE n. This syntax was introduced into the product because - once the derived reportlistener classes in the FFC made alternative output types such as HTML and XML possible - syntax of the form REPORT FORM TO HTML and REPORT FORM TO XML seemed to be required. This would have been shortsighted because there are many more output types that are possible (Word or Excel, anyone?). Instead, the addition of the TYPE n clause to the existing REPORT FORM OBJECT command was implementationally efficient, providing an integer value can be mapped to a given output type. Consider the following command to generate HTML output from a report: REPORT FORM customer.frx OBJECT TYPE 5
Here's how the command is processed by VFP9:
Previewing Reports in object-assisted reportingConsider the following command to display a report preview:
SET REPORTBEHAVIOR 90 Here's how the command is processed by VFP9:
* If the method exists. The preview container object
doesn't have to have a SetReport() method. This lengthy sequence of steps has introduced a new system variable: _REPORTPREVIEW. System Variable: _REPORTPREVIEWFrom the VFP9 Help file: "_REPORTPREVIEW Specifies the application used by Visual FoxPro to generate instances of a report preview user interface in order to satisfy requests from instances of the ReportListener class." Similar to _REPORTBUILDER and _REPORTOUTPUT, _REPORTPREVIEW specifies a program or application. The task of this application is to instantiate a class – a preview container – that can be used to display a preview of the report, using the OutputPage() method of a report listener, along with the cached rendered pages it manages. _REPORTPREVIEW specifies the Report Preview UI Factory application. The program must accept a single parameter:
The Preview Container APIIn order to function as a useful preview window, the object returned from _REPORTPREVIEW must support the Preview Container API. I've discussed this in more detail in another article.
Introducing ReportPreview.AppThe default preview container factory is provided in the form of an application, ReportPreview.App. In addition to the core requirements of a preview container described above, the default preview container class returned by ReportPreview.App also features:
How the default preview container uses the resource fileIn previous versions of Visual FoxPro, the report preview window remembers
position and layout settings in the resource file, if SET
Note: In Service Pack 2, due to a slight change in how the DATA column is written, the ID for preview container preference records has been changed to "92REPREVIEW". Example: Controlling the default Preview ContainerAlthough the report engine will call _REPORTPREVIEW to obtain a default preview container automatically, there is nothing preventing us from giving a reportlistener a customized preview container up-front. The default preview container exposes a number of properties that we can tweak.
First, let's initialize the ReportOutput.App's listener collection, and set up a "preview" listener (that's output type 1):
CLEAR ALL Now we can obtain an instance of the default preview container:
pc = .NULL. Pre-set some of its properties:
pc.TopForm = .T. And now, give it to the preview listener: _oReportOutput['1'].PreviewContainer = m.pc And run a report preview: REPORT FORM customer PREVIEW We should see our customized preview window. Example: Pre-setting the default preview for your applicationsWe can also make our preferences the default by "wrapping" the call to ReportPreview.App with the code we wrote above. Consider the program, PRESET_PC.PRG:
LPARAMETER pc Now we just need to assign this program to _REPORTPREVIEW: _reportpreview = "preset_pc.prg" Example: Successors in the FFC listenersOne thing that all FFC listener classes share is the ability to operate in a "chain of responsibility" with other FFC report listeners. A listener can be configured as the successor of another listener. This is because support for successors is built-in to their common ancestor class _reportlistener.
Consider the following program:
PRIVATE oRlMaster, oRlHtml Here we are using the Successor property of the primary listener (responsibility: Previewing) to attach a secondary listener (responsibility: HTML output) to the report run. The secondary listener will have its methods invoked immediately after each of the primary listeners methods are called by the report engine. There are some limitations here. Any "native" base class behavior (such as sending rendered pages to the printer automatically) must be performed by the primary listener. We have another good example of successors coming up later on. SummaryAnd that concludes our journey through the outsourced reporting components of Visual FoxPro 9.0. The basic points to take away from this are:
|