FoxPro Developer's Conference '94
FoxPro Report Writer
Lisa C. Slater
This session will explore the features of the Report Writer in FoxPro 2.6, as well as the issues you have to face when you report cross-platform.
The Report Writer doesn't lend itself to description in written text, so our real session notes are contained on your source disk. You'll find two projects. REPORTS.APP is a demonstration .APP for you to run, with several illustrative demos, each with its own README information, which appears during the demo. (You'll find all the READMEs, including this essay, in the included README.DBF.) The FRXs may also have notes and comments in page headers that explain the purpose of each example. Read these carefully.
The standalone program TEMPRPTS.PRG will create DOS-ONLY versions of the reports, which are designed as cross-platform entities in the projects. As explained in the session, you'll want to use DOS-ONLY reports under Windows or on the Mac in some situations where a character-based report is appropriate. You can also use TEMPRPTS to provide "virgin" DOS versions of a report when you want to practice Transporting, or when you don't like the results you've achieved with a Transport and want to start over.
Do not run TEMPRPTS on the session's sample FRXs when you run it as part of REPORTS.APP under Windows. You'll get an Invalid File Descriptor error, because the Windows Project Manager will confuse the ones built into the APP with the ones on disk you may be trying to copy.
When, instead, you MODIFY the cross-platform REPORT copies I've provided, Open them As Is! Don't Transport them, because I've added some platform-specific differences! You can set _TRANSPORT to my little My_TRANS.PRG while you work on these reports, if you want to avoid getting the annoying Transporter dialog.
REPORTS.APP finishes by introducing you to several additional issues we may want to discuss during the sessions.
Note that the necessary tables for the sample reports will be found in the NORMAL\DBFS directory for my RAD session source code, but you will also find them in your FoxPro Tutorial directory (any platform). REPORTS.APP adjusts your FoxPro PATH statement appropriately and creates the necessary indexes.
REPTVAR.PRG (DEMO 1)
REPTVAR1.FRX - DOS version
The first example shows various uses of report variables to keep track of parent records in a SET SKIP report. It's easy to use "suppress repeated values" to make a SET SKIP report show the difference between parent records, but what if you need a count by parent item or a sum of some figure that appears in the parent record? If you use the regular CALCULATE feature, you'll be getting a sum or count for all *detail* lines. You also can't use "suppress repeated values" if you're creating a label that *will* be the same for each detail line, but you only want it to be show once per parent.
"Conditional counts" such as this one are useful for more than just parent records; you can make sure that you get a count only for records you specify using any conditions, like this:
Choosing Calculate -- Sum here would allow for a count of all non-blank entries, useful to provide a meaningful average of survey results.
In the example, I've used the report variable NEWPARENT to count parent records and also to create shaded blocks under the parent record labels, as BROWSE does, instead of using "suppress repeated values". Note the interaction of the NEWPARENT and LASTPARENT variables; their order in the report variable list is crucial to this process. Report variables have their values stored to them per detail band instance, and are evaluated in the same order that they appear in the report variable list. Thus, to find out if I've reached a NEWPARENT, I check the current parent value against LASTPARENT, which is still holding the last parent value. LASTPARENT stores the parent value but occurs later in the report variable list, so it hasn't been evaluated at this point.
The interaction between variables LASTPAGE and NEWPAGE is similar. LASTPAGE stores _PAGENO, but when NEWPAGE is evaluated LASTPAGE hasn't changed yet. Note the way NEWPAGE allows you to repeat labels and other information when you switch pages and don't switch groups. (This can also be done with the "suppress repeated values option" option to "re-set on page".) You can do something similar using the native Report Writer abilities to reprinting a group header when the page changes -- but that doesn't help you with detail information.
Why didn't I just group on the parent information? Because, in a DOS report, you can't stretch a group header or footer. It's often convenient to put group information into the detail band, instead of a header, and tell it to suppress repeated values, so you can stretch it if necessary.
Variable LASTPAGE is not released after the first report. I use it in REPTVAR2.FRX to demonstrate a good way to hold onto a page number for later use, in case you want to run a second report with consecutive page numbers, constituting "chapters" of a larger report.
REPTVAR1.FRX - Windows
If you take the TEMPRPTS-generated DOS-only copy of REPTVAR1 and Transport it in Windows, you find a layout that doesn't work in terms of rows or lines, so the ^N and ^O options are meaningless. You also find that you need to change the way the lines are drawn. Grouped lines with the "corner" characters seem to Transport especially poorly.
Use Wingding characters, rather than pictures, to draw arrow heads -- it's a lot faster and doesn't take as much memory to display or print (plus you don't have to remember where you stashed the relevant .BMPs).
Use the arrow keys for fine layout adjustments, and shift-arrow to re-size objects pixel by pixel, with an eye on the status bar after you've turned on Show Position from the Report menu -- first, use the mouse to "sketch" the layout quickly, using the Snap to Grid Report menu option to handle alignment for you. Realize that, unlike the DOS Report Writer, the Windows Report Writer allows you to paste text directly into the layout.
On the Mac, use Easy Access' Mouse Keys option to make really fine adjustments to the layout. This includes diagonal movement -- really helpful when you're moving one pixel at a time.
But once you've finished playing with the look of the report, and investigating the Windows or Mac Report Writer a little more deeply, you realize that the entire kludge of putting group header information into the detail band is irrelevant in Windows -- group headers and footers can stretch! Now you can group on a parent key, and move group information into the header, with impunity, regardless of the contents of the group header. No more IIF()s, no fake detail lines. I have made that change in the GUI version of the report.
Because I wanted a particular appearance in this report (to match a SET SKIP BROWSE), I also wanted the group header to *appear* to be just another detail line, however. The catch is that the group header is now showing information for the *first* detail record for the group, and you don't want this record repeated in the first instance of the detail band.
This isn't a normal requirement; I just did it to show you that Print When is extremely flexible and that the report variables are still available for your use with a similar strategy. But to fulfill it was a cinch -- I just added a Print When Expression (still no IIF()s and certainly no UDFs!) in the detail band to Print NOT NEWPARENT AND NOT NEWPAGE, and told the expressions to Remove Line If Blank (also in the Print When dialog).
If you ever have a situation like this, in which you're sometimes suppressing all output for a detail instance, rest assured that any calculations you want base on the detail lines will still come out right. Report variables will still sum, or otherwise calculate, information for detail instances that you've suppressed in this manner.
I do the conditional count of groups slightly differently in the GUI Report Writer, since the result of the evaluation has to show up in the group band (before NEWPARENT has had a chance to get a new value-to-store). There are several different possible variations; here I use a FirstGroup variable initialized to .T. and storing .F. for the balance of the report. The GroupCount is now Initialized to IIF(FirstGroup,1,GroupCount+1), re-set on group, and stores its own name.
As you can see, variables can reference each other and reference themselves with impunity. If they reference outside variables, however, they should do so with a special check for the existance of those variables, so that you are errorchecking properly and don't have annoying errors while you are testing reports, like this:
IIF(TYPE("outsidevar") # "C","Company Address Goes Here",outsidevar)
... this method is good for demo versions of your products, as well. If you sell a copy, you can add a small external FXP or APP that brands the appropriate report variables with your user's report header information, such as company name and address. You can also change the contents dynamically at runtime, as shown in REPTVAR2.FRX, using a generic dialog.
REPTVAR2.FRX - DOS and Windows LineBreaking
The second report sets the following requirements: a line must be skipped at programmer-defined intervals, and only a certain number of child records-per-parent should show. The latter requirement is extremely useful in any one-to-many report. For example, SET ORDER in a transactions file to a date sequence, DESCENDING, and show only the last 10 transactions for the customer. The former requirement is useful in a variety of situations, and serves to demonstrate an important difference between DOS and Windows.
In the DOS Report Writer, the conditional line-skipping is done with @; formatting. You can also do this with a group that has no other purpose but to check the status of the detail-line-counting report variable -- create a group header (blank line, using an expression composed of something like " ") when it's time for the line skip.
Either method is easily adjusted for groups; just re-initialize the value of DETL_COUNT on group, as done here, and make sure (if you use the second, group header method) that your special group is innermost.
You'll find a similar grouping technique is very useful for conditional page breaks: group on a report variable, and use a UDF to increment it (by whatever criteria you need) when it's time for the break.
When you reach Windows, however, you find that @; format doesn't work! You can use CHR(13)s instead, to provide line breaks, as you see in my Windows version of the FRX. Use two CHR(13)s, since the first one won't "take" unless there is another character after it.
This method has limitations -- you can't force a page break by REPLICATE()ing CHR(13)s the way we do semi-colons under DOS, because you can't use _PLINENO or PROW() to figure out where you are on the page and how far down you have to move. You find that Windows has a lot more options which allow you to specify "New Page", and the kludge is largely unnecessary.
I direct your attention specifically to the ability to create a page break whenever you have less than a specified number of inches or centimeters after a group header. Group on RECNO(), if you want, and put nothing in the group header or footer! There's an example of this in a README.FRX, designed to print your session notes from README.DBF.
The fullfillment of the second requirement (the child-skipping method) uses the detail-counting report variable DETL_COUNT, too. It also uses a second report variable, LASTREC, which you initialize to a UDF-delivered value and store its own value to itself (so the Report Writer does *not* change its value itself at any point). This UDF is called once per parent record (not too bad) to store the last child record number to LASTREC.
Another UDF at the end of the detail line, SkipKids() checks to see if you've reached a stated number of children (passed as a parameter) and skips the rest if you have. It does nothing but RETURN unless we've reached the stated limit.
One more special use of report variables is shown in REPTVAR2.FRX. Haven't you ever wished that you could assign a default value to some variable initialized outside of a report, such as a date format or a company name? When these variables are un-initialized during testing, when the calling procedure or setup program for this application cannot be run, a test of the report or a Page Preview invariably bombs on each such variable, until you've got them all initialized.
The title band of this example report shows two ways to take care of this problem. In both instances, the output contains a report variable rather than the "outside" variable. The report variable is initialized to the outside one if it is found. In one case, where the outside variable is not found, a default value is automatically assigned for this report test. In the other case, where the outside variable is not found, the generic ASK() function prompts you to accept or alter a suggested default value. When you use a report variable to "insulate" you in this way -- or actually to handle *parameters* that must be passed from the calling procedure -- the IIF() handles your initialization and then you store the report variable's own name to it so it is unchanged for the balance of the report.
A few more notes about the ASK() function: this function is actually just an .SPR given a .PRG extension; the ASK.SCX/SCT are included in your project. As you'll see, it can give you information about data of any type. You pass it a prompt, a value, and a string containing picture and function codes if desired. So that it could be easily divorced from REPTVARS.PJX, it uses all default values for generation; that means that your prompt is limited by the size window DEFINEd by GENSCRN. You might prefer to DEFINE the WINDOW with relation to the LEN() of the passed prompt yourself, and un-check DEFINE WINDOW in the screen generation options, or use the MODIFY WINDOW command after GENSCRN's DEFINE WINDOW line, to adjust the size of the window.
When you use this function under Windows you will find that calling an SPR during a REPORT PREVIEW will not behave itself -- because the Preview is actually a separate Windows application, not part of FoxPro -- unless you DEFINE your GET window IN DESKTOP (using #WCLAUSES, as I've done here).
REPTVAR3.FRX - More UDFs, a Special SQL, & More
Although we should minimize our use of UDFs, there are many occasions on which we think we can't do without them. The classic example is "percent of total" calculations that occur in the detail band. Since the Report Writer takes only one pass through the records, if you need to calculate a sum from which you can derive a percentage that you show in each detail band, you have to CALCULATE the sum yourself, first, before the Report Writer goes through the records for the group. This requires only one UDF on the group break, however, to store the group's total in a report variable so each detail item's percentage can be derived from that figure.
You may find that you have an alternative to this UDF, using a SQL SELECT that groups by the same parent value and provides a sum. Using a RELATION between the resulting cursor and the real file, you can derive the totals from the cursor, like this:
SELECT Ono, SUM(ytdsales) AS
FROM Salesman GROUP BY Ono INTO CURSOR Reporter
INDEX ON OnoTAG Ono
SET RELATION TO Ono INTO Reporter
... now you have the appropriate information for your calculation without the UDF in the report, as you can see if you do the following BROWSE:
Many of the occasions on which you can't avoid a UDF involve the last detail record for a report or group. REPTVAR1.FRX showed you use of the first detail instance for a group or parent in a special way, separate from the other detail instances. There are also many times when you want to know the *last* detail instance for the group and do something special about it. The classic example of this problem is a parent with two children for which you need detail reported. REPTVAR3 shows you all the salesmen for each office followed by its customers.
You can't use a report variable to tell you when you're at the end of a group in the same way as you can use report variables to distinguish the first detail instance for the group. I used to have to use a UDF that SKIPed ahead to see if I was in a new group, in the detail band. Following the rule that fewer calls to UDFs is better, I switched to a UDF in the group header, or initializing a report variable on group, to SCAN ahead and find the last record number. But I now find it easier to use a special SQL SELECT trick for this purpose. I create the relevant relationships using a UDF just once for the report, LASTREC2(), in the title band of the report. (If I didn't have a title band, I could put it in the Page Header, using the expression IIF(_PAGENO=1,LastRec2(),"") to make sure it only executed once.
The SQL SELECT statement in LASTREC2 creates a cursor mirroring the first one of the child tables (Salesman), ordered the opposite way to the actual child table's order for the report. Grouped by the parent table's key, this cursor effectively contains only the last child record for each parent value in the report. Now I can relate the Salesman table to the cursor. As I go through the child values during the report, I know when I've reached the end of Salesman records when I'm not at EOF() in the cursor. At that point, I can output the Customer values.
The actual SQL SELECT in LASTREC2 will require adjustment depending on how you've ordered the child records for each parent in your dataset, but this is quite feasible in most cases.
Although you can now avoid a UDF for establishing when to start the second detail set, you will still need the UDF to output the second detail set. This UDF (CUSTOMER(), in the example report) will run once per group as well, and serves to concatenate all the information into one string.
When you look at CUSTOMER.PRG, note the following facts:
Since the GUI Report Writers can't adjust page length, I've had to limit the number of children concatenated in the Win and Mac version of the UDF to what will fit on a page, or the Report Writer gets terminally confused. Remove the limit on cust_count in the UDF and you will see what I mean.
Be sure to check for boundary conditions (no detail records for the first child, none for the second, etc) in determining when to call the UDF. I use a check for a report variable indicating no Salesman records OR NOT EOF("reporter") (the cursor showing the last Customer record) in an IIF() statement to decide when to call CUSTOMER(), *not* a Print When statement, in the GUIs as well as the DOS Report Writer. This is because Print Whens are evaluated throughout a page *before* any UDFs run in a report!! Since the cursor is created in a UDF in the Title band, if you put the EOF("reporter") check in a Print When, the cursor will not exist yet. Try it in a Print When and see.
All information for the second detail set is being concatenated into one string, and therefore all information must be converted to character type and appropriately padded. You will find the TRANSFORM() function very useful to help you with numeric values, but you should note that it will not perform as expected if you use
the $ template and non-American currency symbols. This concatenation obviously means that you are limited in the formatting you can use. See "Additional Issues" for more suggestions -- but understand that in the GUI world, the Report Writer may not always be your best solution!
REPTVAR3.FRX shows you yet another UDF, T_O_C(), designed to show you that UDFs can take you almost anywhere to go in the Report Writer. In this case, the UDF sets up a cursor dynamically during the run of the report, to provide a table of contents to this report in the form of a second REPORT FORM run after this one. REPTVAR.PRG does a demo of this table of contents report for you.
WINPRINT.PRG (DEMO 2)
The scenario: you have a report that usually goes to the printer, and sometimes you want it to go to file.
What should you do?
To get a legible report-to-file, you need a character-type report. Obviously, you can get this by either:
Using a DOS report, which is
Using a Windows report, which is going to send characters and no printer instructions if you use Windows' Generic Text driver.
If you are doing cross-platform reports, you already have the solution in TEMPRPTS.PRG. Just create a DOS-only copy of the report when you want to report to file, and REPORT FORM using that copy. However if you're taking this approach, considering all the subtle cross-platform differences, why not just leave the whole report as a DOS report and run it that way to the printer too? You can even use the DOS printer drivers in the Windows product, in that case.
So what if you aren't doing a cross-platform report? How do you get the Windows-only report to go to file? The problem revolves around how to get at that Generic Text driver.
The FoxPro for Windows RW stores its printer setup information in the FRX. It can be either:
Default printer (in which case
it uses what's in the user's environment)
Specific printer (you get to tell it to go to a particular place).
Most Windows reports should use Default printer -- let the user worry about installing the correct one. You don't want to set up for Specific printer and then find out that your report doesn't print properly, when necessary, because the user has a PostScript printer that won't accept Generic Text driver input!
MSFT and most people will tell you to edit the WIN.INI file to change the user's current default to Generic Text when you want to print to file. This method has two serious drawbacks:
What if other applications are
What if the user doesn't even have a Generic Text driver installed?
PRINTFIL.APP is designed to take care of both these problems. Although I read the .INI file to make sure a Generic Text driver is available (see GETPRINT()), I simply RETURN .F. if it isn't -- the user will *have* to install that driver. The driver installation does, after all, require a disk to be inserted in the disk drive. You could use GETPRINT() during your application installation procedures, as well as re-checking when you're ready to print to file.
Once I know that the driver is available, I most certainly do not edit the WIN.INI to use it! Instead, I make a temporary copy of the FRX with separate printer setup information when I'm ready to print to file. *This* report (used for nothing but files) is set to Specific printer, Generic Text.
You'll see, in PRINTFIL.PRG, that this is laughably easy to accomplish, using a GENERIC.FRX built into PRINTFIL.APP to store the required information. The information is stored in the Tag and Tag2 fields of the FRX header record -- just place it in the temporary report as needed. You don't have to understand what's stored there. (I certainly don't -- nor do I spend a lot of time delving into the *rest* of the structure of the FRX in 2.6!)
Even a DBF of a couple of fields could do it for you, instead of building in the GENERIC.FRX -- in fact one such DBF could hold information about *many* printer drivers:
CREATE TABLE Pdrivers (driver c(20), tag m, tag2 m)
... you could swap them each into the FRX when necessary if you want to. Just create a dummy report, save it with printer driver information set, and store the driver name and the fields Tag and Tag2 to a record in PDRIVERS.DBF for later SCATTER MEMVAR MEMO and GATHER MEMVAR MEMO into the editable copy of the real report. The Generic Text driver happens to be the only one I need, and for this demonstration I want you to see how it's stored in the FRX itself.
I've arranged PRINTFIL so that it can be used with any FRX, using the following sequence of events in the calling program:
* if printing to file:
SET PRINT TO (filename)
REPORT FORM (temp_rptname) TO PRINT
Meanwhile, the default printer in Windows is untouched, and other apps can print as they need to!
I'd like to direct your attention to the function GetPrint() used by PRINTFIL, which will be useful to you beyond PRINTFIL. Use it:
to learn to check the aspects of the current Windows environment
to be called independently to check the availability of a particular printer driver and its direction to ports in the current Windows environment. GetPrint() defaults to looking for the Generic driver on LPT1, but can look for anything you like.
A section below shows you a Macintosh function (CHECKPRT) to check the current Chooser device for a PostScript printer -- you need something like this if you want to do print-to-file on the Mac.
Printing To File on the Mac
You can print to file on the Mac using a DOS-only report form, which uses the character-based report engine, just as you can in Windows. However, if you print to file with any real FoxPro Mac report, you can only do it if the user has a PostScript printer driver selected in the Chooser.
This isn't as useless as you might think. Many page-setup programs on the Mac can read an encapsulated PostScript file, preserve the Report Writer's formatting, and go on to add additional enhancements as required by the user. In addition, it's possible to use a PostScript "viewer" program to look at the files as a Report Form Preview with all formatting intact.
In the same way as I don't write to the WIN.INI to change the default printer under Windows, and for the same reason, I don't use an .MLB or XCMD to change the printer device in the Chooser. Not only may other applications be printing, but also some Mac extensions control the active printer by bypassing or replacing the Chooser entirely. I can't be sure that my XCMD would work in all cases.
Instead, I've simply created a CHECKPRT.PRG to check to see if the LaserWriter driver is loaded in the Chooser. If so, I've got PostScript capabilities. If not, there's a very good chance that the output to file will simply vanish. However, CHECKPRT just informs the outer program of this result; the user has a chance to change the Chooser device or simply to override my warning, since I can't be sure that their chosen printer driver or method of handling printer drivers is inappropriate.
Along with PostScript output, you may want to take a different, Mac-only, approach to print-to-file, using Print2Pict, a shareware extension you should be able to find on-line. Print2Pict is a Chooser device that operates like many fax printer drivers with which you may be familiar. When it is active, the user can get straight ASCII text output from a Mac FRX, if desired, or create a "postcard" -- a snapshot of the graphical output of the FRX with all formatting preserved -- on disk for later viewing. No external viewing program is required. Print2Pict allows output to a number of different formats, as well.
You'll find CHECKPRT.PRG added to the appropriate README.DBF memo entry. as part of the text. The CHECKPRT code will not be runnable without the appropriate XCMD, which you can find in the FoxForum libraries, and besides it makes calls to my generic dialogs -- it's just to give you a sense of how you'd approach this task. It is obviously easily to adapt the function to check for Print2Pict, or any other printer driver, along with the LaserWriter driver specified in this version.
If you are new to the Mac, you may want to pay particular attention to my handling of the XCMD as well as my copying of the XCMDFILE resource file out of the application so that it is available for use on disk.
Interrupting a Report? How About Restart?
Both the Windows and DOS REPORT FORM command can be interrupted by using an ON KEY LABEL that does a GO BOTTOM (and usually sets a flag so your program knows it was called). However, there's a critical limitation: you must have a UDF someplace in the report to give the keypress time to be evaluated. Otherwise the REPORT FORM is just, after all, a single line of executed code. The keypress goes into the buffer and is not "registered" until the REPORT FORM command completes.
Any simple UDF will do (and it doesn't much matter where you put it in the report)...
Once a report is interrupted, can you restart it again? Using a PRINTJOB and _PBPAGE, you can, in DOS... but not in Windows. I have an alternative method that is much more complex; basically, I re-do the report NOCONSOLE and save the results of all calculations, WHILE _PAGENO # _PBPAGE, then SKIP -1 and REPORT FORM REST TO PRINT. But I haven't included it here, because I haven't thoroughly tested this in Windows and it could be unbearably slow.
The truth is that you may be best off turning off your error handler during a report, if the kinds of problems you expect are printer errors. Let FoxPro (in DOS) or the operating system (in Windows or on the Mac) handle these problems directly, including allowing Windows the privilege of printing a particular range of pages without your intervention, with REPORT FORM PROMPT. You aren't really going to gain anything by re-inventing the wheel here. Remember that if you do interrupt and restart a report by some custom system you run the risk of having inaccurate calculations.
How About More than One Report On A Page?
You can do this with a file in DOS, using REPORT FORM .. TO <filename> ADDITIVE and a few tricks.
In Windows and on the Mac you can APPEND the contents of various reports into a memo field, and then print the memofield as your report. This works nicely to provide a formatted output version of program listings, too.
The same goes for multiple stretching items. If you can concatenate them into one object, you can usually get them to behave in the GUI Report Writers -- if you have them as separate objects following each other, you're likely to have trouble.
However, there are limitations of format here, since each object can have only one font, size, and other enhancements. When you find yourself in the position of torturing the Windows Report Writer to provide output requiring sophisiticated formatting within a block of text, you are probably better off using FoxPro to manipulate the data but sending it to WinWord or some other DDE-capable application to format and print it. Let each application do its job! You will find it well worth your while to learn a few commands of Word Basic that you can DDEExecute() rather than struggling to get the Report Writer to pretend to be a desktop publishing machine.
On the Mac, you can use the PostScript output of the Report Writer, sent to a file, in a page setup program that reads encapsulated PostScript files, to bring the Report Writer format undisturbed and then enhance it.
FoxDoc Report Templates
Speaking of using FRXs for formatted program listings, FoxDoc FRXs are accessible to you to change your FoxDoc listings. The default FRXs are delivered in a 8.5 x 11" page format and are bound into FoxDoc. However, you can customize these templates -- MSFT forgot to ship the default templates with the product! I have included the necessary file, FDRPT.EXE, on your source disks. Now you can prepare edited versions of these template FRXs. Go into the FoxDoc Printing options screen and use the Templates... button there to point FoxDoc toward the edited versions of the template FRXs that you want to use.
More Than One Detail Page For the Report?
The DOS Report Writer has a "flexible" page length. A page with more detail lines than can be accommodated by your real page length can spill over to the next page. In the GUIs, the page length is fixed by the printer drivers and cannot be overridden in this way. Instead, create objects that appear in the layout as overlaid, but only print on alternate pages, using a check of _PAGENO to see where they belong.
Memory Problems and Possible Bugs and Changes?
The best thing to do is to make sure you have the latest shipping version. In my experience, the Report Writer problems that involve memory have undergone considerable tweaking with each release, whether we are talking about Preview memory, repeated REPORT FORM commands, or any other type. In addition, if you are experiencing out of memory problems with complex reports, especially with many levels of grouping, you may want to make sure that you have sufficient disk space for tempfiles the Report Writer may be using to attend to its calculations while the report runs. In Windows, this should include sufficient swapfile space, of course.
The Report Writers have also undergone changes with respect to the sequences of variables, UDFs, and group breaks, in an effort to make the different versions act more like each other. If you rely on complex sequences (report variables that reference each other, UDFs that change the value of report variables, groups that re-set page numbers on break with the page numbers then referenced in report variables and/or UDFs), you must thoroughly check the sequence of evaluation whenever you port to a new platform. In addition, you should check these sequences when you upgrade to a new version of the product. Otherwise, it's quite possible to end up with inaccurate calculations.
There Are More Where These Came From...
I hope you come to the sessions with your own questions! The best way to learn about the Report Writer is to experiment in the process of solving real-life problems. If you bring your own reporting dilemmas to the sessions, we'll have that much more to work on. Please do check through the source code carefully as well; written notes like these will not give you the same "feel" for Report Writer techniques as investigating the report variables, UDFs, and even the formatting tricks contained in th sample reports.
FoxPro Report Writer
(c)1994 Lisa C. Slater