By Colin Nicholls This article series is based on a session I gave at the Advisor Visual FoxPro DevCon in June 2005, in which I described the architecture and extensibility features of the new Report Designer [1] in Visual FoxPro 9.0, and demonstrated the basic techniques that it uses "behind the scenes". In this article, I introduce the Report Builder API and show you how to construct your own simple builder applications. I also introduce the default ReportBuilder application and describe some of its features. In case you hadn't realised, the dialog boxes in the Report Designer are actually implemented in a FoxPro application, with a powerful framework behind it that yields tons of opportunities for extension. The Report Builder APIBut first, before I discuss extensibility, I'm going to talk about the underlying architecture in VFP9 that makes all this possible. Visual FoxPro 9.0 introduces a feature which I like to call the Report Builder API. It consists of the following:
Don't worry, I'll take you through the process in detail: What happens during a Report Designer eventThe Report Designer:
Let's examine each of these parameters in detail: 1st parameter: returnFlagsA numeric value of -1 is passed (by reference) to the report builder program as a placeholder for returning values back to the Report Designer. I'll discuss the possible return values in detail later on. 2nd parameter: eventTypeThis parameter contains a numeric value that indicates what particular event has occurred: Was a properties dialog box invoked? A selection from the menu? Was a report layout opened for editing? The values are documented in the help file[msdn] and also in the FFC\foxpro_reporting.h header file: *-- FRX Report Builder event types #define FRX_BLDR_EVENT_PROPERTIES 1 #define FRX_BLDR_EVENT_OBJECTCREATE 2 #define FRX_BLDR_EVENT_OBJECTREMOVE 4 #define FRX_BLDR_EVENT_OBJECTPASTE 5 : 3rd parameter: commandClausesThis parameter is a reference to an object. The object is actually an instance of the Empty class, with additional properties added to it that indicate what clauses were used on the original FoxPro command line that launched the Report Designer, among other things. The exact properties are documented in the help file[msdn], but you can also see them listed in the Event Inspector (see below). As you'll see, having these clauses available to your program is very useful. 4th parameter: designerSessionIdThis parameter indicates the ID of the data session that the Report Designer is running in. This allows the report builder application to switch back and forth between data sessions so as to interrogate any data tables open in the report deisgner session - either opened manually or automatically by the report's Data Environment. This process is documented quite well in the help file[msdn]. What must the builder process do?Due to the call and response mechanism of Report Designer events, the report builder process must be a modal one. It has all the information it requires in order to read from the FRX cursor, interrogate the environment of the Report Desginer, take action based on the parameters passed to it, and make changes to the FRX cursor. It then terminates, returning control to the Report Designer. At that point, the Report Designer needs to know two things:
The Report Designer can get this information by looking at the returnFlags parameter, which - being passed by reference - can have its value set by the report builder program. About that returnFlags parameterThe Report Designer assumes that the value placed in returnFlags by the builder program is a sum of two individual binary flags:
Some examples: returnFlags = 0 = 000Neither flag is set, so the behavior of the Report Designer will be the same as if there was no report builder program assigned to _REPORTBUILDER. In other words, any changes made to the FRX cursor will be ignored, and the Report Designer will behave as in VFP 8.0 and earlier versions. returnFlags = 3 = 011 = 1+2Both flags are set, so the Report Designer will refresh the report layout in the design window, reloading any changes made to the FRX cursor. Also, if there is any "native" behavior attached to the event that occurred, the Report Designer will prevent it from taking place. This is best understood by looking at a simple example that allows you to choose the values of the return flags. Example: SimpleBuilderConsider the following program: PARAMETERS returnFlags, eventType, commandClauses, designerSessionId WAIT WINDOW ; "eventType = " + TRANSFORM( eventType ) + chr(13) + ; "Data session = " + TRANSFORM( SET("DATASESSION")) + chr(13) + ; "ALIAS() = " + alias() + chr(13) + ; "RECNO() = " + TRANSFORM( recno("frx")) + chr(13) + ; "frx.OBJTYPE = " + TRANSFORM( frx.OBJTYPE) + chr(13) + ; "frx.OBJCODE = " + TRANSFORM( frx.OBJCODE) returnFlags = 0 return This example performs a WAIT WINDOW command, listing certain interesting values at each Designer event that occurs. Save this program as, say, SimpleBuilder.prg and assign it to _REPORTBUILDER: _REPORTBUILDER = "SimpleBuilder.prg" CREATE REPORT Now you should see as you click around the Report Designer, that each event is high-lighted with your little WAIT WINDOW. Try right-clicking and selecting Properties... Try selecting Variables... from the Report menu. Example: EventAlertConsider the following program: PARAMETERS returnFlags, eventType, commandClauses, designerSessionId IF MESSAGEBOX( ; "Event: " + TRANSFORM( eventType ) + chr(13) + ; "RECNO('frx') " + TRANSFORM( RECNO("frx")) + CHR(13) + ; "frx.OBJTYPE: " + TRANSFORM( frx.OBJTYPE) + ; chr(13) + chr(13) + "Allow this event?" ,4)=6 returnFlags = 0 && clear both flags, allow default action to proceed ELSE returnFlags = BITSET( m.returnFlags, 0 ) && NODEFAULT : suppress event ENDIF RETURN This example demonstrates the effect of changing the "NODEFAULT" return flag, using a simple MESSAGEBOX() Yes/No call. Try it out in the Command window: _REPORTBUILDER = "EventAlert.prg" MODIFY REPORT _samples + "\Solution\Reports\colors.frx" You will see the report builder program responding to the first event triggered during a report design session - which happens to be the "report open" event. What will happen if you select "No" to tell the Report Designer to suppress its native behavior?
If you thought that the Report Designer would close, don't feel bad. It's a trick question. There doesn't happen to be any native Designer behavior associated with the "report open" event. You can't prevent the report from being opened for editing. [3] Now select Close from the File menu to close the Report Designer window. An event type of 8 is triggered. What will happen if you choose "no" this time? Well, it turns out that you can prevent the Report Designer from closing if you set the NODEFAULT return flag in response to event type 8. Now select Optional Bands... from the Report menu. An event type of 11 is triggered. You can prevent the Optional Bands dialog from appearing if you set the NODEFAULT return flag. You have now observed the following:
Example: TemplateChooserConsider the following program: PARAMETERS returnFlags, eventType, commandClauses, designerSessionId returnFlags = 0 IF eventType = 7 AND commandClauses.IsCreate IF MESSAGEBOX("Do you want to use a template?",4)=6 LOCAL cReportFile cReportFile = GETFILE("FRX", "Template Report:") IF NOT EMPTY( m.cReportFile ) SELECT frx SET SAFETY OFF ZAP SET SAFETY ON APPEND FROM (m.cReportFile) returnFlags = BITSET( m.returnFlags, 1 ) && refresh layout ENDIF ENDIF ENDIF RETURN First, notice that this example takes no action unless a report layout has just been opened for editing (event type = 7) and it is a newly created layout (commandClauses.IsCreate = true). The program filters the kinds of designer events that it will respond to. If these conditions are met, the program prompts you to select an existing FRX file that it then copies in to the new layout, using standard syntax (ZAP, USE, APPEND FROM, etc). With the FRX layout exposed as a read-write cursor, there is nothing stopping you from wreaking terrible vengence on the contents, including ZAP and APPEND FROM. It really is that simple. All you have to remember to do is set the "REFRESH" bit (returnFlags parameter to 2 to ensure that the Report Designer respects the changes made to the FRX cursor - in this case, a wholesale replacement of the contents! Try it out: _REPORTBUILDER = "TemplateChooser.prg" CREATE REPORT If you choose a source report that contains members in its Data Environment, you will notice that this process duplicates these as well. Introducing ReportBuilder.App - the default builderVisual FoxPro 9.0 includes a report builder application as part of the default installation. By default, _REPORTBUILDER should be set appropriately: _REPORTBUILDER = HOME() + "ReportBuilder.App" ReportBuilder FeaturesIntegrates and provides the user interfaceThe ReportBuilder application integrates with the Visual FoxPro Report Designer using the same report builder API that you explored in the examples above. It replaces the native dialog boxes with alternate versions, implemented in FoxPro itself. As well as being more aesthetically pleasing and ergonomically designed, these new dialogs expose additional functionality that is not available in the native dialog boxes. RedistributableAlong with the other component reporting applications [4], ReportBuilder.App is redistributable with your applications. You also get the source to ReportBuilder.App in the XSOURCE.ZIP file located in the Tools\ subdirectory in Visual FoxPro's home directory. Data-drivenJust like the Form and Class Wizards and Builders in previous versions of Visual FoxPro, the ReportBuilder application uses a table of class definitions mapped to specific Report Designer events. This "event handler registry" table is stored as a read-only lookup resource inside the compiled ReportBuilder application. In normal operation, after being invoked in response to an event in the Report Designer, the ReportBuilder application looks at the currently selected record in the FRX cursor; and uses the OBJCODE and OBJTYPE values together with the event type parameter, and performs a lookup operation in its registry table to obtain the name of a class to instantiate to handle the event. Feature: String Trimming ModeSpeaking of additional functionality, the ability to specify the string trimming mode on Field/Expression layout elements is new to VFP9. It is possible because in VFP9 report printing and previewing uses GDI+ graphics API instead of the older GDI API used in previous versions of FoxPro [5]. Let's take a look a report in VFP 8.0:
This report (fileexpr.frx) shows the results of some common FoxPro functions. However, the Field/Expression control used on the right is not big enough to contain the full text of the evaluated result. As you can see, the expression is truncated, with no clue that you are not seeing the full picture. GDI+ allows several different ways that a truncated string can be expressed. The ReportBuilder application exposes these settings in the Format tab of the new Field/Expression Properties dialog box:
You might expect the default mode to be "Trim to nearest word" so as to ensure backward-compatibility with earlier versions of FoxPro. It's not. "Default Trimming" actually means "Use whatever the default string trimming mode of the ReportListener class that is processing the report". And, by default, that is "Trim to nearest word, append ellipsis":
The decision to use this particular default mode was made mainly because GDI+ requires a little additional width to render text (MSDN includes a good article[msdn] on why this is so), and as a consequence, any reports designed with tight space constraints in previous versions of FoxPro might well experience text expression truncation in VFP9. The visible indication of the truncation is desirable because otherwise it would be easy to miss that it happens at all, unless you were very familiar with the data being displayed. I chose this report because it is also ideal for showing one of the more interesting string trimming modes available under GDI+. See what happens when you change the trim mode of the field/expression control to "Filespec: show inner path as ellipsis":
Now both the head and tail of the text expression is shown. This works with any string, not just file specifications. Manually invoking the ReportBuilder applicationReportBuilder.App is normally called automatically from inside the Report Designer via the _REPORTBUILDER system variable. But there are some features you may not be aware of that are available through the command line. Browsing the FRXAs a developer, you may encounter complex report layouts that don't work the way you expect, and have to resort to browsing the .frx source table in order to debug the problem. The ReportBuilder includes a nice FRX browser dialog for this purpose: DO (_REPORTBUILDER) WITH 2, _samples + "\Solution\Reports\colors.frx" The second parameter is optional. If you leave it out, you will get prompted with a file chooser dialog box. The FRX Table Browser dialog displays the contents of the FRX file in a nice layout with the memo fields broken out for easy viewing:
If you are not familiar with the structure of the FRX source table, you should note that:
Accessing the ReportBuilder Options dialog boxThe ReportBuilder Options dialog box is accessible from any report builder dialog by using the right-click context menu:
If you do not happen to be editing a report, you can get there more directly running the ReportBuilder application in the Command window: DO (_REPORTBUILDER) WITH 1 * or DO (_REPORTBUILDER) The parameter switch is actually optional, you get the Options dialog if you omit it.
From here you can configure how the Reportbuilder operates. I won't say much about the other settings at this time, because I really want to talk about the different ways ReportBuilder responds to Report Designer events. Other command-line parametersThe following table documents all of the available command-line parameters that the ReportBuilder application exposes:
The ReportBuilder's Event Handle ModeIn addition to the "normal" mode of looking up a handler class in its registry table, the ReportBuilder application has three other modes of handling events: Handle Mode: DebugIn this mode, the ReportBuilder application presents a special "debug" dialog. You can choose how to set the returnFlags parameter for every Report Designer event. Handle Mode: Event InspectorI described the ReportBuilder's Event Inspector earlier. In this mode, the Event Inspector window is displayed for every Report Designer event, similar to the Debug handler, although as it is a MESSAGEBOX dialog, you obviously have no choice about the return flags. Handle Mode: Ignore builder events completelyIn this mode, the ReportBuilder application lets every event pass through unhandled, as though it were not assigned to _REPORTBUILDER at all.
Other ReportBuilder application featuresWith the default ReportBuilder application plugged in to the Report Designer, you get the following additional features:
I hope I've given you some insight into the architecture of report builders in Visual FoxPro 9.0, and maybe even given you some incentive to try out some report builders of your own design. You can download a zip file containing the source for this article here: ReportBuilder_extend.zip Footnotes:[1] From now on, if I use the words "Report Designer", please feel free to interpret them as "Report Designer or Label Designer". [2] Even if you are editing a Label layout (source file with an .LBX extension), the buffered layout still has the alias "FRX". [3] This is why "NODEFAULT" is a good name for this flag. You can specify NODEFAULT in any method you like in a FoxPro class, but that doesn't mean that there is always going to be something for it to have an effect on. [4] There are three of them: ReportBuilder.App, ReportPreview.App, and ReportOutput.App. [5] If you SET REPORTBEHAVIOR 80, you can print and preview using the older GDI api in Visual FoxPro 9.0 as well. This backwards-compatible mode has some advantages (such as speed) but any advanced formatting features (such as string trimming mode) won't be available, of course. Acknowledgements:Thanks to Lisa Slater Nicholls and Richard Stanton, without whom the Reporting System in VFP9.0 in its current form would not have existed. |
Extending the Report Designer in VFP9 with Report Builders