Sunday, July 10, 2011

Generic cross data report with lazreport

In the lazreport repository there's a demo app showing how to create a cross data report. It uses two instances of TfrUserDataset: one for the master (row) data and one for the cross (column) data. At first glance the component does not provide another way to build such reports. A deeper look shows the contrary. Here are the steps to build a cross data report with arbitrary number of rows and columns.

WARNING: to follow this guide is necessary basic lazreport knowledge.

Prepare the report

Nothing special here

  • Create an empty report

  • Add a Master Data band

  • Add a Cross Data band

  • Add a Text Object inside the Cross Data

  • In the Text Object put a variable named value: [value]

Add handler to retrieve the value

Those familiar with lazreport will have no problems:

procedure TForm1.frReport1GetValue(const ParName: String; var ParValue: Variant);
if ParName = 'value' then
ParValue := IntToStr(FRow) + ' - ' + IntToStr(FCol);

Just the column and row indexes for demonstration purpose. Using together with matrix like data structures the retrieve of actual data is straightforward.

Set the number of rows and columns

The most attentive developers will notice that no dataset (even the virtual dataset) was linked to each band. In fact running the report at this stage will lead to a blank page.
If the number of columns and rows are previously know just set the Virtual Dataset option for each band. This is not an optimum solution since not always we have that info. Here's how to set the Virtual Dataset record count at runtime:

procedure TForm1.frReport1BeginDoc;
BandView: TfrBandView;
BandView := frReport1.FindObject('MasterData1') as TfrBandView;
BandView.DataSet := '9';
BandView := frReport1.FindObject('CrossData1') as TfrBandView;
BandView.DataSet := '2';

This will create a report with nine rows and two columns. Yep, you read right: lazreport store the number of the records of band's Virtual Dataset in an string field, the same field that store the name of an associated TfrDataset.
WARNING: don't look at lazreport source. It may scare the faint hearted ;-)

Track the row and column positions

The tricky part. The first thing to do is add two integer fields (FRow and FCol) to the Form/Data Module containing the TfrReport instance.

To get the column add an event to OnPrintColumn, and store the ColNo parameter:

procedure TForm1.frReport1PrintColumn(ColNo: Integer; var ColWidth: Integer);
FCol := ColNo;

Notice that the Lazarus IDE will create an event declaration with the second parameter named Width. This will not compile with {$mode ObjFpc}. Renaming it to ColWidth will make the compiler happy.

There's not an event that pass the current row position. The first try is to hook into the OnBeginBand

procedure TForm1.frReport1BeginBand(Band: TfrBand);

Running the report with this will show wrong row indexes because it will increment in all bands not only the Master/Row band. The fix is easy:

procedure TForm1.frReport1BeginBand(Band: TfrBand);
if Band.Typ = btMasterData then

It's done, add Data Header and Cross Header bands, glue with actual data and the generic cross data report is done. The sample project.


José Torres said...

Thanks! this helps me and my coworkers to learn how to deal with lazreport. Obrigado :)

Durali Kiraz said...

Thank my friends.