Printing columns reports that span accross multiple pages

Category

Code Based - General

Question

How can we produce a column report that spans multiple pages? Applications for this would be to:
a. List the contents of an entire table no matter how many columns the table has
b. Produce a cross tabular report such as a multi-dimensional report from a pivot query, or multi-dimensional report

Solution

The solution that I have here was done using BCB 4.0, but it should be relatively simple to convert it into delphi code. It essentially prints the the contents of a table, spanning it over multiple pages if necessary. Firstly lets create a small utility function that will generate the maximum string for each column

AnsiString PaddedString( AnsiString Text, int MaxLength )
{
  char Filler[ 1024 ];
  memset( Filler, ' ', 1024 );
  Filler[ 1023 ] = 0;
  Text += Filler;
  Text.SetLength( MaxLength );

  return Text;
}

Put in the code to execute the report

void __fastcall TForm1::JfBitBtn4Click(TObject *Sender)
{
  ReportSystem2->Execute();
}

Firstly open the table and initialize the parameters. I have decided to process the table by columns, therefore the variables [B]FirstFieldToPrint[/B] and [B]LastFieldToPrint[/B] will be used to keep a track of which columns we are currently processing.

void __fastcall TForm1::ReportSystem2BeforePrint(TObject *Sender)
{
  TBaseReport * rp = dynamic_cast< TBaseReport * >( Sender );

  Table1->Open();
  Table1->First();

  FirstFieldToPrint = 0;
  LastFieldToPrint = 0;

  FooterPen = rp->CreatePen( clRed, psSolid, -5, pmCopy );
}

We need to print out the headers for each of the table columns. As we are processing the report according to columns, I have decided to: 1. Print the column header on the first page of each column 2. Set tabs to make it easier to print the data in the correct positions as each column value is printed

void __fastcall TForm1::ReportSystem2PrintHeader(TObject *Sender)
{
  TBaseReport * rp = dynamic_cast< TBaseReport * >( Sender );

  rp->SetFont( "Arial", 12 );
  rp->AdjustLine();
  rp->Bold = True;

  //Define the columns
  if( Table1->Bof )
  {
    //Clear the Current Tabs and move to the starting position on the page
    rp->ClearTabs();
    rp->GotoXY( rp->LeftWaste, rp->TopWaste );

    //Initialize our variables
    FirstFieldToPrint = LastFieldToPrint;
    bool PageWidthReached = False;

    //Create tabs across the page, while we have enough room on the page
    while( !PageWidthReached && LastFieldToPrint < Table1->FieldCount )
    {
      TField * Field = Table1->Fields->Fields[ LastFieldToPrint ];

      AnsiString Text = PaddedString( Field->DisplayLabel, Field->DisplayWidth );

      PageWidthReached = ( rp->XPos + rp->TextWidth( Text ) ) > ( rp->PageWidth - rp->RightWaste );

      //Once the page width is reached, Set a tab and leave a gap for the
      //next column header.  Then increment the LastFieldToPrint
      if( !PageWidthReached )
      {
        rp->SetTab( rp->XPos, pjLeft, rp->TextWidth( Text ), 0, BOXLINENONE, 0 );
        rp->GotoXY( rp->XPos + rp->TextWidth( Text ) + 0.25, rp->YPos );

        if( LastFieldToPrint + 1 < Table1->FieldCount )
          LastFieldToPrint++;
      }
    }
  }

  //Move to the beginning of the current line, and print out the field values
  rp->GotoXY( 0, rp->TopWaste );
  for( int i = FirstFieldToPrint; i < LastFieldToPrint; i++ )
  {
    rp->PrintTab( Table1->Fields->Fields[ i ]->DisplayLabel );
  }

  rp->NewLine();
  rp->Bold = False;
}

Next we print out the body of the report, using the tab positions that we defined when we printed the column heading

bool __fastcall TForm1::ReportSystem2PrintPage(TObject *Sender,
      int &PageNum)
{
  TBaseReport * rp = dynamic_cast< TBaseReport * >( Sender );

    rp->SetFont( "Arial", 10 );
    rp->AdjustLine();

    for( ; !Table1->Eof; Table1->Next() )
    {
      for( int i = FirstFieldToPrint; i < LastFieldToPrint; i++ )
      {
        rp->PrintTab( Table1->Fields->Fields[ i ]->AsString );
      }

      rp->NewLine();

      if( ( rp->LinesLeft() - 3 ) <= 0 )
        return True;
    }

    //Print the Table Contents
    if( Table1->Eof )
    {
      if( LastFieldToPrint + 1 >= Table1->FieldCount )
        return False;
      else
        Table1->First();
    }

  return True;
}

Optionally, we can then print out our footer information at the bottom of the report.

void __fastcall TForm1::ReportSystem2PrintFooter(TObject *Sender)
{
  TBaseReport * rp = dynamic_cast< TBaseReport * >( Sender );

  rp->GotoXY( rp->LeftWaste, rp->PageHeight - rp->BottomWaste );
  rp->Print( "Page " + rp->Macro( midCurrentPage ) + " of " + rp->Macro( midTotalPages ) );

  rp->SetPen( FooterPen->Color,  FooterPen->Style, FooterPen->Width, FooterPen->Mode );
  rp->MoveTo( rp->LeftWaste, rp->PageHeight - rp->BottomWaste - rp->LineHeight );
  rp->LineTo( rp->PageWidth - rp->RightWaste, rp->PageHeight - rp->BottomWaste - rp->LineHeight );
}

Finally, the table can be closed and resources freed

void __fastcall TForm1::ReportSystem2AfterPrint(TObject *Sender)
{
  Table1->Close();

  delete FooterPen;
}

From here, you can use this basic idea to then develop virtually any type of cross tabular report that you like.