devxlogo

Extending the Visual FoxPro 9 Reporting System

Extending the Visual FoxPro 9 Reporting System

here are incredible improvements in the Visual FoxPro 9 reporting system. Of several aspects, I’ll discuss just one in this article: the ability to extend the behavior of the runtime reporting engine.

The Visual FoxPro development team had several goals in mind when they worked on the runtime improvements, including:

  • Handling more types of report output than just printing and previewing
  • Using GDI+ for report output. This provides many significant improvements, such as more accurate rendering, smooth scaling up and down of images and fonts, and additional capabilities, such as text rotation.
  • Providing a more flexible and extendible reporting system

You have access to both the old report engine and the new one, so you can run reports under either engine as you see fit. But once you see the benefits of the new report engine, you won’t want to go back to old-style reporting.

Reporting System Architecture
Before Visual FoxPro 9, the report engine was monolithic; it handled everything and with a few exceptions (user-defined function, expressions for OnEntry and OnExit of bands, and so forth), you couldn’t interact with it during a report run.

The new reporting engine splits responsibility for reporting between the report engine, which now deals only with data handling and object positioning, and a new object known as a report listener, which handles rendering and output. Because report listeners are classes, we can now interact with the reporting process in ways we could only dream of before.

The reporting engine in Visual FoxPro 9 splits responsibility for reporting between the report engine, which now just deals with data handling and object positioning, and a new object known as a report listener, which handles rendering and output.

Report listeners produce output in two ways: page-at-a-time and all-pages-at-once. Page-at-a-time mode renders a page and outputs it, then renders and outputs the next page, and so on. This mode is used when a report is printed. In all-pages-at-once mode, the report listener renders all the pages and caches them in memory. It then outputs these rendered pages on demand. This mode is used when a report is previewed.

New Reporting Syntax
Visual FoxPro 9 supports running reports using the old report engine; you use the REPORT command just as you did before (although, as you’ll see in a moment, you can use a new command to override the behavior of REPORT). To get new-style reporting behavior, use the new OBJECT clause of the REPORT command. The OBJECT clause supports two ways of using it: by specifying a report listener and by specifying a report type. Microsoft refers to this as object-assisted reporting.

A report listener is an object that provides new-style reporting behavior. Report listeners are based on a new base class in Visual FoxPro 9 called ReportListener. To tell Visual FoxPro 9 to use a specific listener for a report, instantiate the listener class and then specify the object’s name in the OBJECT clause of the REPORT command. Here’s an example:

   loListener = createobject('MyReportListener')   report form MyReport object loListener

If you’d rather not instantiate a listener manually, you can have Visual FoxPro do it for you automatically by specifying a report type:

   report form MyReport object type 1

The defined types are:

  • 0 for outputting to a printer
  • 1 for previewing
  • 2 for page-at-a-time mode but not to send the output to a printer
  • 3 for all-pages-at-once mode but not invoke the preview window
  • 4 for XML output
  • 5 for HTML output.

You can also use other, user-defined types.

When you run a report this way, the application specified in the new _REPORTOUTPUT system variable (ReportOutput.APP in the Visual FoxPro home directory by default) is called to determine which listener class to instantiate for the specified type. It does this by looking for the listener type in a listener registry table built into the APP (although you can also tell it to use an external table). If it finds the desired class, it instantiates the class and gives a reference to the listener object to the reporting engine. Thus, using OBJECT TYPE SomeType in the REPORT command is essentially the same as:

   loListener = .NULL.   do (_ReportOutput) with SomeType, loListener   report form MyReport object loListener

ReportListener
During the run of a report, Visual FoxPro exposes reporting events to objects based on the ReportListener base class as they happen. The Visual FoxPro Help file has complete documentation on the properties, events, and methods (PEMs) of ReportListener, but I’ll only discuss the most useful ones in this article.

Table 1 lists the most commonly used properties of the ReportListener class.

Table 1: Some useful properties of the ReportListener class.

Property

Description

CurrentDataSession

The data session ID for the report’s data

FRXDataSession

The data session ID for the FRX cursor

GDIPlusGraphics

The handle for the GDI+ graphics object used for rendering

ListenerType

The type of report output the listener produces. The default is -1, which specifies no output, so you’ll need to change this to a more reasonable value. The values are the same as those specified in the OBJECT TYPE clause of the REPORT command.

OutputPageCount

The number of pages rendered

QuietMode

.T. (the default is .F.) to suppress progress information

Table 2 lists the most commonly used events and methods of ReportListener.

Table 2: Some useful events and methods of the ReportListener class.

Event/Method

Description

LoadReport

Fires before the FRX is loaded and the printer spool is opened

UnloadReport

Fires after the report has been run

BeforeReport

Fires after the FRX has been loaded but before the report has been run

AfterReport

Fires after the report has been run

BeforeBand

Fires before a band is processed

AfterBand

Fires after a band is processed

EvaluateContents

Fires before a field is rendered

Render

Fires as an object is being rendered

OutputPage

Outputs the specified rendered page to the specified device

ReportListener Subclasses
The FFC (FoxPro Foundation Classes) subdirectory of the Visual FoxPro home directory includes _ReportListener.VCX, which contains some subclasses of ReportListener that have more functionality than the base class. The most useful of these is _ReportListener.

The _ReportListener class lets you chain listeners by providing a Successor property that may contain an object reference to another listener.

One of the most important features of _ReportListener is support for successors. You may want to use more than one report listener when running a report. For example, if you want to both preview a report and output it to HTML at the same time, more than one report listener must be involved. _ReportListener allows chaining of listeners by providing a Successor property that can contain an object reference to another listener.

For example, suppose ListenerA and ListenerB are both subclasses of _ReportListener that each perform some task, and you want to use both listeners for a certain report. Here’s how you can chain these listeners together:

   loListener = createobject('ListenerA')   loListener.Successor = createobject('ListenerB')   report form MyReport object loListener

The report engine only communicates with the listener specified in the REPORT or LABEL command, called the lead listener. As the report engine raises report events, the lead listener calls the appropriate methods of its successor; the successor calls the appropriate methods of its successor, and so on down the chain. This type of architecture is known as a chain of responsibility, as any listener in the chain can decide to take some action or pass the message on to the next item in the chain.

Another interesting capability of _ReportListener is chaining reports. The AddReport method adds a report to the custom ReportFileNames collection. You pass this method the name of a report and optional report clauses to use (such as the RANGE clause) and a reference to another listener object. The RemoveReports method removes all reports from the collection. RunReports runs the reports; pass it .T. for the first parameter to remove reports from the collection after they’re run and .T. for the second parameter to ignore any listeners specified in AddReport. Here’s an example that runs two reports as if they were a single report:

   loListener = newobject('_ReportListener', ;   home() + 'ffc\_ReportListener.vcx')   loListener.ListenerType = 1   loListener.AddReport('MyReport1.frx', ;     'nopageeject')   loListener.AddReport('MyReport2.frx')   loListener.RunReports()

HTML and XML Output
Because one of the design goals of the development team was to provide additional types of report output, Visual FoxPro 9 includes two subclasses of _ReportListener, called the HTMLListener, and the XMLListener, providing HTML and XML output, respectively. These listeners are built into ReportOutput.APP but are also available in _ReportListener.VCX.

Listener type 5 specifies HTML output and type 4 is for XML output, so you could just use the following command to output to HTML:

   report form MyReport object type 5

However, this doesn’t give you any control over the name of the file to create or other settings. Instead, call ReportOutput.APP to give you a reference to the desired listener, set the desired properties, and then tell the REPORT command to use that listener.

The following code creates an HTML file called MyReport.HTML from the MyReport report. When you specify type 5, ReportOutput.APP uses its built-in HTMLListener class to provide output.

   loListener = .NULL.   do (_reportoutput) with 5, loListener   loListener.TargetFileName = 'MyReport.html'   loListener.QuietMode = .T.   report form MyReport object loListener

The following code creates an XML file called MyReport.XML from the MyReport report, containing only the data. In this case, the snippet uses the XMLListener class (type 4).

   loListener = .NULL.   do (_reportoutput) with 4, loListener   loListener.TargetFileName = 'MyReport.xml'   loListener.QuietMode = .T.   loListener.XMLMode = 0   && 0 = data only, 1 = layout only, 2 = both   report form MyReport object loListener

HTML output actually uses the XML listener to produce XML and then uses XSLT to produce the HTML end-result.

Both of these listener classes have additional properties you can use to further control the output. I recommend a look at the Visual FoxPro documentation for details. Also, as they are subclasses of _ReportListener, listener classes support the capabilities of the _ReportListener class, including chaining listeners and running multiple reports. Here’s an example that outputs to both XML and HTML at the same time:

   use _samples + 'NorthwindOrders'   loListener1 = .NULL.   do (_reportoutput) with 4, loListener1      loListener1.TargetFileName = 'MyReport.xml'      loListener1.QuietMode = .T.      loListener1.XMLMode = 0         && 0 = data only, 1 = layout only, 2 = both      loListener2 = .NULL.   do (_reportoutput) with 5, loListener2      loListener2.TargetFileName = 'MyReport.html'      loListener2.QuietMode = .T.      loListener1.Successor = loListener2   report form MyReport object loListener1

Creating Your Own Listeners
Because report listeners are a class, you can create subclasses that alter the behavior of the reporting system when a report runs.

The key to changing the way a field appears in a report is the EvaluateContents method.

For example, one thing I’ve always wanted is a way to dynamically format a field at runtime. Under some conditions, I may want a field to print with red text, and under others, I want it in black. Perhaps a field should sometimes be bold and the rest of the time not.

The key to changing the way a field appears in a report is the EvaluateContents method. This method fires for each field object just before it’s rendered, and gives the listener the opportunity to change the appearance of the field. The first parameter is the FRX record number for the field object being processed and the second is an object containing properties with information about the field object. (See the Visual FoxPro Help file for a list of the properties this object contains.) You can change any of these properties to change the appearance of the field in the report. If you do so, set the Reload property of the object to .T. to notify the report engine that you’ve changed one or more of the other properties.

Listing 1 shows some code (TestDynamicFormatting.PRG) defining a subclass of _ReportListener, called EffectsListener, that handles different types of effects that may be applied to fields in a report. These effects are applied by effect handler objects, which are stored in a collection in the oEffectsHandlers property of EffectsListener. Each effect handler object handles a single effect

As the report is processed, the listener needs to determine which fields will have effects applied to them. It does that in the EvaluateContents method by looking at each field as it’s about to be rendered. EvaluateContents calls SetupEffectsForObject, which calls the GetEffect method of each effect handler to let it decide whether to apply an effect to the field. GetEffect looks in the USER memo of the field’s record in the FRX for a directive indicating what type of effect to apply. If a particular handler is needed for the field, a reference to the handler is added to a collection of handlers that processes the field (as a field may have more than one effect applied to it).

However, EvaluateContents fires for every field in every record, and there really isn’t a need to check for the effects more than once for a particular field (doing so would slow down the performance of the report). So, BeforeReport creates an array with as many rows as there are records in the FRX.

If the first column of the array is the default .F., the listener hasn’t checked for the effects of the field being rendered yet, so EvaluateContents does that and then sets the first column of the array to .T. so the FRX record isn’t examined again.

After determining whether there are any effects to be applied to the field, EvaluateContents then goes through the collection of effect handlers for the field, calling the Execute method of each one to have it do whatever is necessary.

DynamicForeColorEffect is one of the effect handlers. It looks for a directive in the USER memo of a field in the report with the following format:

   *:EFFECTS FORECOLOR = expression

(You can access the USER memo of an object in a report from the Other page of the properties dialog box for that object.)

The ORDERDATE field of the TestDynamicFormatting report used in Listing 1 has the directive in the following snippet in its USER memo; it tells EffectsListener that the DynamicForeColorEffect object should adjust the color of the field so it displays in red if the date shipped is more than ten days after it was ordered or black if not:

   *:EFFECTS FORECOLOR = iif(SHIPPEDDATE > ORDERDATE +       10, rgb(255, 0, 0), rgb(0, 0, 0))
?
Figure 1: TestDynamicFormatting Report. The code in Listing 1 produced this report, which shows dynamic formatting for the Shipped Date and Ship Via columns.

The Execute method of DynamicForeColorEffect changes the color of the field by setting the PenRed, PenGreen, and PenBlue properties of the field properties object that was passed to EvaluateContents to the appropriate colors and sets Reload to .T., which tells the report engine that changes were made.

DynamicStyleEffect uses a similar directive to change the font style. The style to use must be a numeric value: 0 is normal, 1 is bold, 2 is italics, and 3 is bold and italics. The SHIPVIA field in the TestDynamicFormatting report has the following directive in USER, which causes the field to display in bold if SHIPVIA is 3 (which, because of the expression for the field, actually displays as Mail) or normal if not:

   *:EFFECTS STYLE = iif(SHIPVIA = 3, 1, 0)

DynamicStyleEffect works the same as DynamicForeColorEffect, but changes the Style property of the field properties object.

Running TestDynamicFormatting.PRG results in the output shown in Figure 1.

Custom Rendering
You aren’t limited to changing the appearance of a field?you can do just about anything you like in a report listener. The Render method of ReportListener is responsible for drawing each object on the report page. You can override this method to perform various types of output.

A listener that performs custom rendering will almost certainly have to use GDI+ functions to do so.

A listener that performs custom rendering will almost certainly have to use GDI+ functions. GDI+ is a set of hundreds of Windows API functions that perform graphical manipulations and output.

To make it easier to work with GDI+ functions, Visual FoxPro includes _GDIPlus.VCX in the FFC directory. _GDIPlus, which was written by Walter Nicholls of Cornerstone Software in New Zealand, consists of wrapper classes for GDI+ functions, making them both easier to use and object-oriented. The “GDI Plus API Wrapper Foundation Classes” topic in the Visual FoxPro Help file lists these classes and provides a little background about them. This library is a great help in doing GDI+ rendering because you don’t have to know much about GDI+ to use them; I certainly don’t and yet was able to create the listener class discussed next in just a couple of hours.

?
Figure 2: This is the TestColumnChart.FRX as it looks at design time.

The code shown in Listing 2, taken from TestColumnChart.PRG, runs the TestColumnChart.FRX report, shown in Figure 2, and creates the output shown in Figure 3. Notice how different the output looks than the report layout suggests; the fields and shape don’t appear but a column chart graphing the contents of the Category_Sales_For_1997 view in the sample Northwind database does. That’s partly due to Print When clauses on the fields that prevent them from printing, but the biggest reason is that the listener class used for this report, ColumnChartListener, replaces the shape object in the Summary band with a column chart. Let’s see how this listener does that.

The Init method of ColumnChartListener initializes the aColumnColors array to the color to use for each column in the chart. Note that GDI+ colors are a little different than the values returned by RGB(), so the CreateColor method is used to do the necessary conversion. If you want to use a different set of colors, you can subclass ColumnChartListener or store a different set of colors in the array after you instantiate ColumnChartListener. Note that only eight colors are defined; if there are more than eight columns in the chart, the listener uses the same colors for more than one bar.

?
Figure 3: The code in Listing 2 produced this report, which creates a column chart rather than traditional output.

The BeforeReport method instantiates a GPGraphics object into the custom oGDIGraphics property. GPGraphics is one of the classes in _GDIPlus.VCX. It and other _GDIPlus classes are used in the DrawColumnChart method to draw the components of the column chart.

GPGraphics needs a handle to the GDI+ surface being rendered to. Fortunately, the listener already has such a handle in its GDIPlusGraphics property. The only complication is that the handle changes on every page, so the BeforeBand method, which fires just before a band is processed, calls the SetHandle method of the GPGraphics object to give it the handle when the title or page header bands are processed.

As the report is processed, the listener needs to determine where the labels and values for the chart come from. It does that in the EvaluateContents method by looking at each field as it’s about to be rendered. If the USER memo for the field’s record in the FRX contains LABEL (as it does for the CategoryName field), that indicates that this field should be used for the labels for the column chart. DATA in the USER memo (as is the case for the CategorySales field) indicates that the field is used as the values for the chart. As with the EffectListener class I discussed earlier, there isn’t a need to check the USER memo more than once, so the same type of mechanism?storing a flag in an array property to indicate whether the field was processed?is used in this class.

If the listener hasn’t checked the USER memo for the field being rendered yet, EvaluateContents does that, setting flags in the array indicating whether the field is used for labels or values, and setting the first column of the array to .T. so the FRX record isn’t examined again. If the field is used for either labels or fields, EvaluateContents updates the aValues array accordingly.

AdjustObjectSize is similar to EvaluateContents except it fires for shapes rather than fields. AdjustObjectSize checks the USER memo of the FRX record of the current shape for the presence of COLUMNCHART, indicating that this shape is to be replaced by a column chart. As with EvaluateContents, the listener only needs to check once, so it uses similar logic.

The Render method is responsible for drawing an object on the report. If the object about to be drawn is a shape to be replaced with a column chart, it calls the custom DrawColumnChart method followed by NODEFAULT to prevent the shape from being drawn. Otherwise, the object is drawn normally. (Notice there’s no DEDEFAULT(); the native behavior is to draw the object, so it isn’t required).

DrawColumnChart figures out the maximum of the values being charted so it knows how big to draw the columns, and then it creates some objects from _GDIPlus classes needed to perform the drawing. It calls the DrawLine method to draw the vertical and horizontal borders for the chart, and then goes through the aValues array, drawing a column for each value using DrawRectangle and filling it with the appropriate color via FillRectangle. DrawColumnChart adds a box and label to the legend for the chart using the same DrawRectangle and FillRectangle methods for the box and DrawStringA for the label.

Some drawing attributes come from values in custom properties, making charting more flexible. For example, cLegendFontName and nLegendFontSize specify the font and size to use for the legend label, and nLegendBoxSize specifies the size of box to draw. See the comments for these properties near the start of the Listing 2.

Microsoft has blown the lid off the Visual FoxPro reporting system! By passing report events to ReportListener objects, they’ve allowed us to react to these events to do just about anything we wish, from providing different types of output to dynamically changing the way objects are rendered. I can hardly wait to see the type of things the Visual FoxPro community does with these new features.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist