Subject Re: Report group headers
From Mervyn Bick <invalid@invalid.invalid>
Date Fri, 15 Dec 2023 10:59:26 +0200
Newsgroups dbase.getting-started
Attachment(s) test_count.rep

On 2023/12/14 23:47, Charlie wrote:
> I have report where I am using group headers.  I am finding that on some pages a group header will appear at the bottom of the page, then the details on the next page.  Is there a way of preventing this from happening?  Thanks

Short answer, yes.  In practice, as Ken point's out, it's complicated.
It is, however, not particularly difficult once one gets one's head
around it.

The report designer is extremely powerful but using it isn't as
intuitive as using the form designer.  Ken's book is invaluable when one
needs more than just a plain vanilla report.  Believe me, it will be
money well spent.

Basically, one needs to determine how much space is needed for a
specific group's header, its footer and how ever many records are in the
group before one actually renders the group.

As each band is prepared for rendering it updates its renderOffset
property which gives the distance from the top of the streamframe to the
bottom of the band.  The renderOffset value is saved when the band is
rendered.  This allows one to use the group's FOOTERBAND_onRender()
event handler to calculate how much of the streamframe is still
available after a group has been rendered.  If there's enough room for
the next group with it's header, records and footer, carry on.  If there
isn't enough room, set the group headerband's beginNewFrame property
true which will move to the next page.

The attached example (which was done as a learning exercise back in
2014) is based on code from Ken's dBASE Reports book for counting the
records in a group.  Ken's code in the book and in his examples is fully
commented.  My code, which was created for learning rather than
teaching, has very few comments.  I understood what needed to be done
and I was more interested in getting it working.  You should, however,
be able to follow the code which is actually quite straightforward.

A few comments about the example.  I have no idea why I used twips as
the metric as I normally use centimetres.  The principle is, however,
the same no matter what metric is used.

Although the detailband height is shown as 250 this was just a starting
point before any controls were added.  The detailband's variableHeight
property is set true and one gets its actual height for use in the
calculation by summing the top and height properties of an appropriate
control on the detailband.  In this case all controls are aligned so any
control will do.

The example was created while I was still learning.  Nowadays, as there
is nothing on the group footerband, I would set its height to 0.  The
footerband's onRender event will still be triggered even if there are no
controls on the footerband.  It would, however, affect the spacing on
the report.

The example only works where each control on the detailband occupies one
line.  It is also "all or nothing" when it comes to deciding whether
there is enough room for a group or not.  With groups containing many
records this can lead to unacceptably long blank spaces at the the
bottom of pages.  Ken's book deals with splitting long groups over pages
with the appropriate "continuation" text in the header on the new page
while at the same time ensuring that one doesn't wind up with a single
"orphan" line at the bottom of a page or at the top of the new page.

If one of the controls displays data from a memofield, which may need
several lines and the number of lines can vary from record to record,
then simply counting records is not enough.  One also needs to calculate
how many lines the memofield will need for each record.  This is a far
more complicated exercise.  I do, however, have an example for this so
just ask if you need it.

Mervyn.








clear

** END HEADER -- do not remove this line
//
// Generated on 2014-10-15
//
local r
r = new test_countReport()
r.render()

class test_countReport of REPORT
   with (this)
      autoSort = false
   endwith

   this.DBASESAMPLES1 = new DATABASE(this)
   with (this.DBASESAMPLES1)
      left = 1080.0
      top = 1080.0
      width = 360.0
      height = 360.0
      databaseName = "DBASESAMPLES"
      active = true
   endwith

   this.EMPLOYEES1 = new QUERY(this)
   with (this.EMPLOYEES1)
      left = 1080.0
      top = 1080.0
      width = 360.0
      height = 360.0
      database = form.form.dbasesamples1
      sql = "select e.*,substring(lastname from 1 for 1) as intial from employees e order by lastname"
      requestLive = false
      active = true
   endwith

   this.PAGETEMPLATE1 = new PAGETEMPLATE(this)
   with (this.PAGETEMPLATE1)
      height = 16837.0
      width = 11905.0
      marginTop = 1080.0
      marginLeft = 1080.0
      marginBottom = 1080.0
      marginRight = 1080.0
      gridLineWidth = 0
   endwith

   this.PAGETEMPLATE1.STREAMFRAME1 = new STREAMFRAME(this.PAGETEMPLATE1)
   with (this.PAGETEMPLATE1.STREAMFRAME1)
      height = 11592.0
      left = 360.0
      top = 1365.0
      width = 9360.0
      form.STREAMFRAME1 = form.pagetemplate1.streamframe1
   endwith

   with (this.printer)
      duplex = 1        // None
      orientation = 1        // Portrait
      paperSource = 268
      paperSize = 9
      resolution = 3        // Medium
      color = 2        // Color
      trueTypeFonts = 1        // Bitmap
   endwith

   this.STREAMSOURCE1 = new STREAMSOURCE(this)
   this.STREAMSOURCE1.GROUP1 = new GROUP(this.STREAMSOURCE1)
   with (this.STREAMSOURCE1.GROUP1)
      groupBy = "intial"
   endwith

   with (this.STREAMSOURCE1.GROUP1.footerBand)
      onRender = class::FOOTERBAND_ONRENDER
      height = 250.0
   endwith

   with (this.STREAMSOURCE1.GROUP1.headerBand)
      height = 250.0
   endwith

   this.STREAMSOURCE1.GROUP1.headerBand.TEXTINTIAL1 = new TEXT(this.STREAMSOURCE1.GROUP1.headerBand)
   with (this.STREAMSOURCE1.GROUP1.headerBand.TEXTINTIAL1)
      height = 300.0
      left = 390.0
      top = 35.0
      width = 1530.0
      variableHeight = true
      prefixEnable = false
      text = {||this.form.employees1.rowset.fields["intial"].value}
   endwith

   with (this.STREAMSOURCE1.detailBand)
      height = 250.0
   endwith

   this.STREAMSOURCE1.detailBand.TEXTEMPLOYEEID1 = new TEXT(this.STREAMSOURCE1.detailBand)
   with (this.STREAMSOURCE1.detailBand.TEXTEMPLOYEEID1)
      height = 293.0
      left = 525.0
      top = 348.0
      width = 1350.0
      variableHeight = true
      prefixEnable = false
      alignHorizontal = 2        // Right
      text = {||this.form.employees1.rowset.fields["employeeid"].value}
   endwith

   this.STREAMSOURCE1.detailBand.TEXTFIRSTNAME1 = new TEXT(this.STREAMSOURCE1.detailBand)
   with (this.STREAMSOURCE1.detailBand.TEXTFIRSTNAME1)
      height = 293.0
      left = 2115.0
      top = 348.0
      width = 1530.0
      variableHeight = true
      prefixEnable = false
      text = {||this.form.employees1.rowset.fields["firstname"].value}
   endwith

   this.STREAMSOURCE1.detailBand.TEXTLASTNAME1 = new TEXT(this.STREAMSOURCE1.detailBand)
   with (this.STREAMSOURCE1.detailBand.TEXTLASTNAME1)
      height = 293.0
      left = 3990.0
      top = 348.0
      width = 1530.0
      variableHeight = true
      prefixEnable = false
      text = {||this.form.employees1.rowset.fields["lastname"].value}
   endwith

   this.STREAMSOURCE1.detailBand.TEXTHIREDATE1 = new TEXT(this.STREAMSOURCE1.detailBand)
   with (this.STREAMSOURCE1.detailBand.TEXTHIREDATE1)
      height = 293.0
      left = 6015.0
      top = 333.0
      width = 1080.0
      variableHeight = true
      prefixEnable = false
      text = {||this.form.employees1.rowset.fields["hiredate"].value}
   endwith

   with (this.reportGroup.footerBand)
      height = 250.0
   endwith

   with (this.reportGroup.headerBand)
      height = 0.0
   endwith

   this.firstPageTemplate = this.form.pagetemplate1
   this.form.pagetemplate1.nextPageTemplate = this.form.pagetemplate1
   this.form.pagetemplate1.streamframe1.streamSource = this.form.streamsource1
   this.form.streamsource1.rowset = this.form.employees1.rowset

   function FOOTERBAND_onRender
      local oStream, nCount, nSpaceRemaining, nSpaceNeeded,cInitial
      oStream = this.parent.parent
      oStream.bookMark = oStream.rowset.bookMark()
      if not oStream.rowset.endofset
         oStream.rowset.next()
         nCount = 0
         cInitial = substr(oStream.rowset.fields["lastname"].value,1,1)
         do while  oStream.rowset.fields["lastname"].value = cInitial  ;
            and not oStream.rowset.endofset
            oStream.rowset.next()
            nCount ++
         enddo
         oStream.rowset.goto(oStream.bookmark)
      endif
      nSpaceRemaining = this.streamframe.height - this.renderOffset
      nSpaceNeeded =  250+250+(nCount*641)  //space for header, footer and rows
      if nSpaceRemaining < nSpaceNeeded
         this.parent.headerband.beginNewframe := true
      else
         this.parent.headerband.beginNewframe := false
      endif
      return

endclass