Reporting with dBASE Plus:
A Tutorial

Michael Nuwer
October 28, 2002

Session V
Conditional Page Breaks

Goals and Objectives

The goals for this Session are:

Introduction

When you need to force a new page in your report, you must use the beginNewFrame() method of the streamSource. For example, if you have a grouped report and you want to start each group on its own page, you can call the beginNewFrame() method in the onRender event handler of the group footerBand.

You can also determine the amount of space remaining on your page with the renderOffset property. This is a property of the groupHeaderBand, groupFooterBand, or detailBand. The renderOffset property returns the distance from the top of the streamFrame to the bottom of the band. This measurement, subtracted from the height of the streamFrame, is the distance to the bottom of the printable page.

The combination of the beginNewFrame() method and the renderOffset property give us the tools to force new pages.

Pagebreaks based on detail data

Lets consider the first page break report. Assume we need to force a new page based on a condition of the data that we are printing. Perhaps we have an inventory table and we want all the parts whose part number begins with "AAA" to print on one page and those beginning with "BBB" to print on a new page.

Open Paging1.rep in the report designer. This report lists the litter date, the Dam and the Sire from the litters.dbf table. How can we begin each month on a new page?

Lets look at forcing a new page for each month in the Litter date. (The litter dates in this report are restricted to one year. Because of this we don't need to worry about the year.)

Start by creating a calculated that returns the month of the litter date.

function litters1_onOpen
   local oField
   oField=new Field()
   oField.FieldName := "CalcMonth"
   this.rowset.fields.add(oField)
   oField.BeforeGetValue := ;
   {; Return month(this.parent["lit_date"].value) }
   return

Next, we need to initialize a custom property to keep track of the current month. We will do this in the onOpen event handler of the Text field that displays the date. The custom property is going to be assigned to the streamSource1 object.

 Function KMFIELDTEXT1_onOpen
   this.parent.parent.curMonth = 1
   return

This event will fire once -- when the report object is created -- and will sets the custom property. When the report renders, and begins looping through the data, we will compare the next row of data with the custom property.

To do this we need an onRender event handler for the detailBand. In the code the row pointer will skip down one row in the rowset, compare the month in that record with the current month, and if they are not the same, we will force a new page. Here is the code for this procedure.

 Function DETAILBAND_onRender
   local oStream, nNextMonth
   oStream = this.parent
   // move row pointer one row down
   oStream.rowset.next(1)
   // store next month value for next row
   nNextMonth = oStream.rowset.fields["calcMonth"].value
   if nNextMonth # oStream.curMonth and not oStream.rowset.endOfSet
      // reset the property
      this.parent.curMonth = nNextMonth
      // force a new page
      oStream.beginNewFrame()
   endif
   // move row pointer back one row
   oStream.rowset.next(-1)
   return

You can run this report and see that each month will print on it's own page.

Pagebreaks based on lines remaining

The next report is going to force a new page if the child records within a group will not fit on the remaining space of the current page. To do this we need to know how many children records need to be printed and compare that with the amount of space on the page.

Open Paging2.rep in the report designer. This report displays parent and child data, and is grouped by the parent record. If you run this report and skip through the pages, you will notice that the groups are not always kept together on the same page. To change the report we need to add some code to the groupFooterBand's onRender event handler.

In the report designers add this event handler and then enter the following code:

 function FOOTERBAND_onRender
   local oStream, oChild
   local nChildCount, nSpaceRemaining, nSpaceNeeded
   // create some reference shortcuts
   oStream = this.parent.parent
   oChild = this.parent.parent.parent.litters1
   // set a bookmark on the current parent row
   oStream.bookmark = oStream.rowset.bookmark()
   // skip down to the next parent row
   oStream.rowset.next(1)
   // set range on the child rowset
   oChild.rowset.setRange( oStream.rowset.fields["REG_NO"].value )
   // count the number of child records 
   nChildCount = oChild.rowset.count()
   // remove the range restriction
   oChild.rowset.clearRange()
   // return the row point to the bookmark
   oStream.rowset.goto(oStream.bookmark)
   // determine the amount of space remaining on the page
   nSpaceRemaining = this.streamFrame.height - this.renderOffset
   // determine the about of space needed to print the bands
   // headerBand + detailBand + footerBand         
   nSpaceNeeded = 0.9 + (nChildCount * 0.3) + 0.3  
   // uncomment the following for debugging
   /*
   ? '------------------------------------'
   ? ' child. num.: ' + nChildCount
   ? ' rest: ' + nSpaceRemaining
   ? ' needed: ' + nSpaceNeeded
   */
   // determine if there is enough space to print the next group
   // if not, force a new page
   if nSpaceRemaining < nSpaceNeeded 
      oStream.beginNewFrame()
      // ? '***New Page***'
   endif
   
   return

Although the inline comments explain what each line is doing, a few comments are needed. To determine the space needed, we need to sum the height of the groupHeaderBand, the groupFooterBand, and the detailBand for each child record in the group (nChildCount). It's a bit tricky to determine the values needed for these heights. Sometime the height of a band is fixed, so the value is known. However, the height property defaults to zero, which allows the band to adjust so that each individual row is as big as necessary. In the current report it's a good idea to specify the height all the time. Also make sure you set the expandable property for each band to false.


Go to next Session
Go back to the tutorial menu

The Legal Stuff: This document is part of the dBASE Reporting Tutorial created by Michael Nuwer. This material is copyright © 2002, by Michael Nuwer. dBASE, dBASE Plus, dBASE SE, and dB2K are copyrighted, trademarked, etc., by dBASE, Inc., the BDE (Borland Database Engine) and BDE Administrator are copyrighted, trademarked and all that by Borland, International. This document may not be posted elsewhere without the explicit permission of the author, who retains all rights to the document.