Browse DevX
Sign up for e-mail newsletters from DevX


Extending the Visual FoxPro 9 Reporting System : Page 5

In this article, you'll learn about Visual FoxPro 9's report listener concept, how it receives events as a report runs, and how you can create your own listeners to provide different types of output in addition to print and preview.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

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.

Doug Hennig is a partner with Stonefield Software Inc. He is the author of the award-winning Stonefield Database Toolkit (SDT), the award-winning Stonefield Query, and the MemberData Editor, Anchor Editor, and CursorAdapter and DataEnvironment builders that come with Visual FoxPro. Doug is co-author of the "What's New in Visual FoxPro" series and "The Hacker's Guide to Visual FoxPro 7.0." He was the technical editor of "The Hacker's Guide to Visual FoxPro 6.0" and "The Fundamentals." All of these books are from Hentzenwerke Publishing. Doug has spoken at every Microsoft FoxPro Developers Conference (DevCon) since 1997 and at user groups and developer conferences all over North America. He is a Microsoft Most Valuable Professional (MVP).
Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date