Consider a report which requires cycling a detail dataset (ADataSet) to generate simple 1-line-per-record output.

You could use a TDetailFrame to provide a looping structure to cycle these records, OR you could code your own loop to cycle them.

Assuming we execute the code for this task from the OnRow event of a TPageFrame (PageFrame1), lets look at both approaches:


Using a TDetailFrame to Cycle Records

One approach is to conditionally execute a TDetailFrame (DetailFrame1) only if records exist in the dataset, before finally closing the dataset:

procedure TForm1.PageFrame1Row(ReportInterface: TReportInterface;

ReportWriter: TReportWriter; ReportFrame: TReportFrame; var Valid: Boolean);

begin

ADataSet.Open;
try

if not ADataSet.IsEmpty then

DetailFrame1.Execute(ReportWriter);

finally

ADataSet.Close;

end;

end;

Then you can output the line of data in DetailFrame1.OnRow (lets just print a field "Name" for each record):

procedure TForm1.DetailFrame1Row(ReportInterface: TReportInterface;

ReportWriter: TReportWriter; ReportFrame: TReportFrame; var Valid: Boolean);

begin

ReportInterface.PrintLine(ADataSet.FieldByName('Name').AsString);

end;

As we only need 1 line per record, we can set property DetailFrame1.BandRow.MinLines := 1 in the Object Inspector. This means that if there is not at least 1 line available to print the row, DetailFrame1 will automatically start a new page.

Following this output, we can progress to the next record in DetailFrame1.OnRowAfter and end the loop when no more records exist.

procedure TForm1.DetailFrame1RowAfter(ReportInterface: TReportInterface;

ReportWriter: TReportWriter; ReportFrame: TReportFrame; var Valid: Boolean);

begin

ADataSet.Next;
Valid := not ADataSet.EOF;

end;

This is fairly straight forward. The TDetailFrame events logically separate output code (in OnRow) from dataset navigation and control code (in OnRowAfter), although there are no rules to say you have to always do this. You could just as easily move the OnRowAfter code to the OnRow event handler in this case.


Using a Coded Loop to Cycle Records

Rather than use a frame component like TDetailFrame, you can code a while loop to cycle through those same records instead. You can check for available space within this loop, starting a new page as required. This code achieves the same result as the code above:

procedure TForm1.PageFrame1Row(ReportInterface: TReportInterface;

ReportWriter: TReportWriter; ReportFrame: TReportFrame; var Valid: Boolean);

begin

ADataSet.Open;
try

with ReportInterface do
begin

//cycle the dataset records
while not ADataSet.EOF do
begin

//check if enough space (1 line required)
if not EnoughBandLines(1) then
begin

//start a new page
NewPage;

//reset this bands configuration on the new page
ReportFrame.BandRow.ResetBand;

end;

//output the record
PrintLine(ADataSet.FieldByName('Name').AsString);

//go to the next record
ADataSet.Next;

end;

end;

finally

ADataSet.Close;

end;

end;

Aside from introducing the while loop, a key difference here is that we must check for available space ourselves, and call NewPage if required. Starting a new page probably fires off other bands (such as a PageHeader) so it is important to "reset" the current band to reinstate the correct font and tab settings. In this case we are in the PageFrame.OnRow event, so a call is made to ReportFrame.BandRow.ResetBand where the ReportFrame parameter represents the current frame (ie PageFrame1).


Which Method Should You Choose?

Well, at the end of the day it doesn't matter too much either way for this simple example. Using a TDetailFrame is probably easier to code. It may just be a matter of convenience or preference.

On the other hand, there can be some overhead to cycling through a frame loop with each cycle repeatedly checking for and setting up child bands. DetailFrame1, for example, will setup its BandRow once for each cycle. If no other bands (such as BodyHeaders and BodyFooters, or GroupHeaders and GroupFooters) are involved with the loop, this effort is redundant (unless starting a new page where we force a band setup in the above code)... so a coded loop may be more efficient and give more "readable" code. If some of these other bands are involved, then the frame will be required to incorporate and cycle through them.

One particular reason why a coded loop might be useful is if you have more complex space requirements. Band minimum space properties only accommodate either a fixed number of even-height-lines (MinLines) or a fixed height (MinHeight) for automatic page breaking. If the height of each row varied depending on the particular field values in the DataSet record, for example, then testing the space requirements "manually" in code is required.

Of course, you can still manually test space requirements when using a TDetailFrame. Leaving the band MinLines and MinHeight properties set to zero means there will be no automatic page-breaking. You must provide the space check yourself, similar to that illustrated above, and thus effectively use a combination of both approaches.

This is one of the advantages of "code-based reporting" - report structure is very flexible!