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
|
|