This intermediate-level session will give you tips, techniques, and a solid background, to help you ring changes on VFP reporting.
VFP’s Report Forms get results from your data by co-ordinating some disparate elements:
At first, these varied components may seem as unfocused and awkward as an orchestra tuning up! But, with the proper mix and arrangement, you can bring these performers together into a REPORT FORM command that really sings.
These notes will discuss the elements of the VFP reporting process listed above, with an attempt to identify some common problems and solutions for each step of the process. My experience with VFP developers and reporting leads me to believe that the particular points herein are of value to almost everybody who uses the REPORT FORM command to create VFP output.
However, there are almost unlimited variations of these problems – and many other related issues – which you may personally have experienced. Although I hope you find something of value here, my sessions will only be based loosely around this outline of the reporting process. I expect to take questions from the floor that will provide other specific examples, as we go through this outline during the session.
I hope people do their best to surprise me (and the Report Writer!) during the sessions. I will do my best to make this additional material available to you – please see my notes at the end of this paper to explain how.
The part of VFP reporting that seems most confusing to developers is the Data Session and Data Environment that were added to the FRX in VFP 3. Where does VFP data come from, from the point of view of a report?
To dispel some of this confusion, let me start by explaining that „Default“ versus „Private“ data sessions have a different meaning than people seem to think. „Default“ doesn’t mean „the base session belonging to the Command window and Screen (SET DATASESSION TO 1)“. It means „the current session when the report is called.
This is the same meaning as „Default“ data session has to a form. If you call a modal dialog from another form with a private session, that dialog shares the session of its caller form. Similarly, if you run a REPORT FORM from within the same modal dialog, that report form shares the session with the dialog and the original form. It has access to all the tables open in that session.
By contrast, a „private datasession“ (for either our modal dialog or report form) will not have access to the original form’s tables. You can open tables and move pointers with impunity, knowing that this private session will disappear when the dialog or report does, and that the underlying session of the caller form will not be touched.
So which should you use? As so often in VFP, „it depends“! Sometimes you need access to the current data in a form (where the user is entering new information and you want to print a confirming copy, for example). In that case you should leave the DataSession setting at „Default“. (You’ll find this setting is a toggled option on the Report menu popup).
At other times, the inviolate nature of the private session will „free“ the report from dirty data, and give you an exact snapshot of the state of the system at a particular moment. For example, suppose you are printing an end-of-month listing. You could SELECT into cursors that belonged to the private data session of the report, and that would not change even though people outside might continue to enter data after the cut-off point at which you decided to run the report.
The concept of a shared data session has added one extra complexity to report design, for many people: if they are designing a report interactively, they can’t „see“ the calculated fields and other data that might be available to them within the calling form. The Expression Builder simply doesn’t know what to show in the Fields list. If you are using a private data session, all you have to do is put the tables in the Data Environment and the Expression Builder will „know about“ them.
Whether you use a default or private session, if your data elements come from tables and views in a DBC, you can open the Data Designer and drag and drop the appropriate fields to become report expressions. This will obviate the need to worry about whether the Expression Builder „knows about“ the tables and views. However, be warned: the tables and views are being added to the Data Environment as you do this – which may not be what you want. You can remove all the tables from the DE when you’re finished designing, of course.
The Expression Builder is a born tyrant in the Report Designer. It wants to add aliases to all report expressions (see figure below). Since you may want to use a report for multiple queries or views under different aliases, you may also want to remove these aliases if you use the EB to design your expressions.
Why is the Field Aliases option button group disabled when you use the Expression Builder from the Report Designer? I haven’t a clue
Perhaps the EB is trying to tell us something. Now that we have the ability to have multiple data sessions, whether the report is sharing a form’s or using its own, perhaps we should just use a report with uniform aliases all the time. After all, there is no reason why these aliases should change even though the tables (or even the nature of the data sources) change from report instance to report instance. We can even write Data Environment method code to swap different data tables in „underneath“ the various cursors in the DE at runtime.
If your report depends on other runtime values, however, you may want to take advantage of the actual conditions of the calling form while you are attempting to design the report, verify report expressions, and so on.
If you have this requirement, just DO the FORM when you are ready to design the report! In fact DO your application and SUSPEND when the form is open, if you need a lot of setup and especially if the form is modal. SET DATASESSION TO the form’s session, in the Command Window, at that point (you can use the DataSession (View) window to do this). Now drop into design mode, and CREATE your REPORT with all elements „in place“. This will allow you to preview your report as you work, with all elements properly initialized, even if your report event call form methods to retrieve values or runtime conditions.
This instantiation of the calling form is really just an extension of the way we used to have to make sure any report UDFs and external variables were in scope, and all required queries were run in Fox 2.x, if we wanted to preview reports while we designed.
While we’re on the subject of UDFs, with the new report events in VFP, we have more and better reasons to attach code to FRXs than we ever did before – but this doesn’t mean you will necessarily have more UDFs. Even if you don’t use report environments for anything else, the ability to attach code to a Data Environment method is one good reason to love the VFP Report Designer. As you’ll see later in this paper, I use DE methods that otherwise would never get used for everything I can think of, and say good by to UDFs that have to be „packaged“ with a report! Whether at design-time or run-time, the report always knows exactly where those methods are.
What if you want to use UDFs with a report? There are probably many good reasons to continue to use UDFs with reports. For one thing, there are only so many „disposable“ methods available to us in a report DE (although each cursor and relation you add to the DE gives you more methods to play with!).
Some of your UDFs may want to reference DE cursors as objects, or they may want to call DE methods.
For this reason, VFP gave us the NAME clause on the REPORT FORM command. When you use the following syntax:
REPORT FORM <filename> NAME oReference
... you’ve named a variable as a reference to the Data Environment object hosted by the report. (Remember that the report itself is not an object.) So, for example, using this reference I could check oReference.Cursor1.Filter or oReference.Cursor2.Order using the reference I’d created with the NAME clause.
Since this reference needs to be globally available for the duration of the report (for reasons you’ll see in my examples below), I don’t find the NAME clause that useful. It’s just as easy for me to put the following code in Data Environment methods to take care of the reference myself:
oDE = THIS
Now that reference is available to me wherever I need it throughout the life of the report.
What are some reasons you might want to use this reference? As you’ll see, I use DE methods in group expressions and other parts of the report that were probably not what they were designed for, because it aids in encapsulating the report’s behavior. To do this, I need a public reference with which I can refer to the DE object.
Right now I’d like to suggest two other, special ways you could use a DE reference or methods. These tricks don’t happen to appear in my examples, but they are both variations of one of the most common questions about reports on the Fox newsgroups.
How do you pass parameters to a report? The answer is, officially, that you can’t. There’s no clause of the REPORT FORM command that will allow you to do it, and no place in the report to put a PARAMETERS line.
However, with a little help from the DE, you can easily get information „from the outside“ into the report. For example, the DE and its member objects have some properties that they don’t actually use for much at runtime (such as Top and Left). You can place information in these properties using the DE object reference. You can also use a DE method such as Init or BeforeOpenTables to call a method of THISFORM or some other object reference, and place the result in a report variable where it can be sed for the balance of the report.
There are at least two reasons to use #DEFINEd constants in code:
We can’t derive that second benefit, in reports, but we can certainly take care of the first one with a little help from the DE: put your #DEFINEs into a DE method and transfer the results into report variables in that method.
There is one major reason to use #INCLUDE files in code: it gathers all these constants into one or more easily edited external files. It is perfectly plausible to put an #INCLUDE file in a DE method and, once more, transfer the results into report variables at that point.
For example, I sometimes write reports in which all strings have to be localized for different language requirements. I „initialize“ these strings in the DE, as follows:
rcTitle = APPLICATION_NAME_LOC + „ „ + LEDGER_REPORT_TITLE_LOC
rcBudget = BUDGET_LOC
rcDetail = DETAIL_LOC
rcTotal = TOTAL_LOC
Each of these „rc*“ variables is a report variable. Report variables are public, and quite available by the time you get to the DE.Init, so I can store the #DEFINEs into report variables and use these report expressions, rather than literal strings in report expressions for the balance of the report.
Gone are the days when a Fox report session was all about sizing and moving objects around in the layout window! Now that Designers and their layout options (grouping, formatting, and aligning objects, keyboard shortcuts, font choices, etc) are fairly standard on the Windows platform, I think you can all handle most of this without my having to say much on this subject.
There are certainly deficiencies in the formatting-capabilities of the Report Designer which cannot be „fixed“ with tricks. If you find yourself irritated with these dead ends, your frustration is most likely aggravated by the fact that you have had more positive experiences, both in Fox and elsewhere. In other Windows products, you may have experienced a more finely-tuned layout and design tool. In Fox, you’ve developed the habits of properly subclassing and inheriting format attributes and behavior, and you don’t see why things should be different in a report.
If either of these feelings is plaguing you, don’t fight with the FRX too hard. We do have non-REPORT FORM methods of creating output in Fox (I’ll talk about these in my other session), but even in the FRX, help is at hand. Markus Egger has delved into the mysteries of the FRX format to deliver some functionality that can be described by the FRX table but to which we do not have access in the Designer interface. His GENREPOX tool also allows report objects to „inherit“ features from other objects in what he calls „FRX Classlibs“ – à la Ken Levy’s GENSCRNX – with the use of a few comment snippets.
You can see how you use GENREPOX in the figure below. As the comment snippet for the Copyright item in this report shows, this report object inherits behavior from two classes (a Copyright class and a Page One class). It clears any font information from those classes so that, without re-specifying an override explicitly, this object will retain font information as shown in the current report.
GENREPOX can actually do a lot more than this, and supplies hooks for you to do still more. Markus supplies an HTML driver that uses these hooks, and there are many other directions you can take this tool in. GENREPOX is in public domain, so you can include it in your applications (it will be required at runtime if you want GENREPOX-enhanced behavior in your reports). As Markus puts it : „actually it’s beerware... use it as often as you want, but if you meet me, buy me a beer... <s>“. Do drop by http://www.foxgang.net/eps to pick up a copy and to thank Markus for his work.
GENREPOX by Markus Egger