Composite Report built from Multiple Existing Reports

PolomintPolomint Australia
edited 7:29AM in FastReport VCL 5
OK, I know this is something I should be able to resolve from the documentation or source code, but...

We have an application that produces a number of reports at end of Financial Year. We've been asked if we can simplify the task of running the reports, by "batching them up". The obvious answer was to create a Composite Report.

Each Report is set up in separate Delphi Units, with their own frxReport component and FR3 definition file. The consolidated report is also in its own Unit with a frxReport / FR3 that creates a cover-page, and then pulls in the constituent parts. One of the key design requirements was to make only minimal changes to the original reports, and to have most of the unique work done in the consolidating report's Unit.

This is working reasonably well except for a few issues.

#1 - the total page count is right, but each constituent report starts counting pages from 1.

Rather than dump the whole application here, I've created a test program and reports that reproduce the problem.

Attached is the PDF export of the report produced from three basic reports (also attached). The code that generates the report is:
procedure TFormTestFastReports.ActionReportsCompositeExecute (Sender : TObject);
begin
  frxReportComposite1.PrepareReport ();
  frxReportComposite2.PrepareReport ();
  frxReportComposite1.PreviewPages.AddFrom (frxReportComposite2);
  frxReportComposite3.PrepareReport ();
  frxReportComposite1.PreviewPages.AddFrom (frxReportComposite3);
  frxReportComposite1.ShowPreparedReport;
end; {TFormTestFastReports.ActionReportsCompositeExecute}
Each of the frxReportComposite* objects has EngineOptions.DoublePass set TRUE.

The containing program includes the PDF Export component, which was used to generate the file from the Preview Form.

#2 - the user wants to have blank pages between the constituent reports to ensure each "sub-report" starts on an odd-numbered page.

We've tried using frxReportComposite1.PreviewPages.AddEmptyPage to do this, but cannot work out how
- to test the report for number of pages "so far"
- how to set the value of the single parameter (seems to only accept zero!)


#3 - can we get rid of the "cover page"?

The equivalent of the Test Example's frxReportComposite1 in the User Application is currently producing a simple cover sheet page, with no data. The user doesn't want it! We've tried starting with an empty report, but just got a lot of AVEs and Memory Leaks.

====

I've tried searching in these Fora for an answer, but there seems to be very little on the subject...

Cheers, Paul

Comments

  • PolomintPolomint Australia
    edited August 2017
    Oh and we are using FR VCL 5.6.3 (Pro) with RAD Studio Seattle >

    Answer to part (a) of #2 may be to use frxReport.PreviewPages.Count, which seems to return the expected value...
  • gpigpi
    edited 7:29AM
    1. Use one TfrxReport instance for correct Page#
    frxReportComposite1.LoadFromFile('1.fr3');
    frxReportComposite1.PrepareReport (True);
    frxReportComposite1.LoadFromFile('2.fr3');
    frxReportComposite1.PrepareReport (False);
    frxReportComposite1.LoadFromFile('3.fr3');
    frxReportComposite1.PrepareReport (False);
    
    2. Use report template with empty TfrxReportPage (not empty TfrxReport)
    3. Use TfrxReport.PreviewPages.DeletePage
  • PolomintPolomint Australia
    edited August 2017
    -- CORRECTED POST --
    gpi wrote: »
    1. Use one TfrxReport instance for correct Page#
    2. Use report template with empty TfrxReportPage (not empty TfrxReport)
    3. Use TfrxReport.PreviewPages.DeletePage
    Thanks for the suggestions, GPI.

    However,
    - As explained in the original post, we cannot use "one TrfxReport" as the > 20 reports are built using "stand alone" Units, and because they are complex (some include charts, all have many report-specific BeforePrint events for Header / Detail / Footer Bands etc.) - refactoring the Application Reporting into a monolith would be time-consuming, risky, and (to be frank) create a maintenance nightmare. We may have to hack the FastReport code, or see if we can do something with the underlying templates, common code, and "user variables".
    - We've discovered that there is no need to introduce an empty TfrxReportPage. - Using AddEmptyPage(0) we can prefix the second and subsequent "sub-reports" with a blank page (when needed) before adding to the Composite
    - How exactly should we use DeletePage? - A trial run (code below) has had no effect on the Cover Page ("Report1"). See the PDF file attached.

    Tantalisingly close but not there yet!
    procedure TFormTestFastReports.ActionReportsCompositeExecute (Sender : TObject);
      var
        PreviewPagesCount : Integer;
    
    begin
      frxReportComposite1.PrepareReport ();
      frxReportComposite1.PreviewPages.DeletePage (0); // tried Index as 1 and 0 both
    
      frxReportComposite2.PrepareReport ();
      PreviewPagesCount := frxReportComposite1.PreviewPages.Count;
      if (PreviewPagesCount mod 2) <> 0 then
        frxReportComposite2.PreviewPages.AddEmptyPage (0);
      frxReportComposite1.PreviewPages.AddFrom (frxReportComposite2);
    
      frxReportComposite3.PrepareReport ();
      PreviewPagesCount := frxReportComposite1.PreviewPages.Count;
      if (PreviewPagesCount mod 2) <> 0 then
        frxReportComposite3.PreviewPages.AddEmptyPage (0);
      frxReportComposite1.PreviewPages.AddFrom (frxReportComposite3);
    
      PreviewPagesCount := frxReportComposite1.PreviewPages.Count;
      frxReportComposite1.ShowPreparedReport;
    end; {TFormTestFastReports.ActionReportsCompositeExecute}
    
  • gpigpi
    edited 7:29AM
      frxReport1.PrepareReport(True);
      frxReport1.PrepareReport(False);
      frxReport1.PreviewPages.DeletePage(0);
    
    frxReport1.PreviewPages.DeletePage(0); will not work for one-page report
  • PolomintPolomint Australia
    edited 7:29AM
    gpi wrote: »
    frxReport1.PreviewPages.DeletePage(0); will not work for one-page report
    OK, well I've reworked the test programme and tested it.

    Most of what we want of the Composite of existing stand-alone reports is now fully addressed by a couple of "tricks".
    - DeletePage (0) after all "sub-reports" added but before the call to ShowPreparedReport
    - Prefixing the second and subsequent reports with a blank page (AddEmptyPage) only if there is currently an even number of pages in the Composite


    I've also checked our Application, in the Test Environment, using this technique. Seems to work well...

    Now we just need to work out how to number the pages correctly in the footers. >
    procedure TFormTestFastReports.ActionReportsCompositeExecute (Sender : TObject);
      var
        PreviewPagesCount : Integer;
    
    begin
      // Cover Sheet will be deleted at end
      frxReportComposite1.PrepareReport ();
      // First "sub-report"
      frxReportComposite2.PrepareReport ();
      frxReportComposite1.PreviewPages.AddFrom (frxReportComposite2);
      // Second (and subsequent) "sub-reports"
      frxReportComposite3.PrepareReport ();
      PreviewPagesCount := frxReportComposite1.PreviewPages.Count - 1;
      // Add empty page if current set of pages (ignoring the Cover Sheet) is an "odd" count
      if (PreviewPagesCount mod 2) <> 0 then
        frxReportComposite3.PreviewPages.AddEmptyPage (0);
      frxReportComposite1.PreviewPages.AddFrom (frxReportComposite3);
      // Delete the Cover Sheet
      frxReportComposite1.PreviewPages.DeletePage (0);
      // now show the report
      frxReportComposite1.ShowPreparedReport;
    end; {TFormTestFastReports.ActionReportsCompositeExecute}
    
  • PolomintPolomint Australia
    edited 7:29AM
    OK - we can now wrap this one up. I detail how it was done in case any one else wants to augment an existing application without too much pain...

    Turns out that correctly numbering the pages in the Composite (with blank pages "for formatting purposes") didn't require anything more exotic than a user-defined variable containing the "base page number", and a small change in the footer of our inherited report "template" (to replace [Page#] with [<Page>+<BasePageNo>]. The actual application and 20+ reports make use of a lot of shared code, so we leveraged that to avoid too many changes and future maintenance headaches.

    The "Test Application" shows simply how this was done, using "in line" code rather than objects / shared-functions, and the attached PDF confirms that it works!

    Hope you find it useful.

    Cheers, Paul

    See also: https://www.fast-report.com/documentation/P..._a_variable.htm
    procedure TFormTestFastReports.ActionReportsCompositeExecute (Sender : TObject);
      var
        RequiresBlankPage : Boolean;
        PreviewPagesCount : Integer;
    
    begin
      // Cover Sheet will be deleted at end
      with frxReportComposite do begin
        PrepareReport ();
      end;
      // First "sub-report" just added assuming an empty "base"
      with frxReportPart1 do begin
        with Variables.Add do begin
          Name := 'BasePageNo';
          Value := 0;
        end;
        PrepareReport ();
      end;
      frxReportComposite.PreviewPages.AddFrom (frxReportPart1);
      PreviewPagesCount := frxReportComposite.PreviewPages.Count - 1;
      RequiresBlankPage := (PreviewPagesCount mod 2) <> 0;
      // Second (and subsequent) "sub-reports" added assuming "base" has pages already
      with frxReportPart2 do begin
        with Variables.Add do begin
          Name := 'BasePageNo';
          if RequiresBlankPage then
            Value := PreviewPagesCount + 1
          else
            Value := PreviewPagesCount;
        end;
        PrepareReport ();
      end;
      // Add empty page if current set of pages (ignoring the Cover Sheet) is an "odd" count
      if RequiresBlankPage then
        frxReportPart2.PreviewPages.AddEmptyPage (0);
      frxReportComposite.PreviewPages.AddFrom (frxReportPart2);
      PreviewPagesCount := frxReportComposite.PreviewPages.Count - 1;
      RequiresBlankPage := (PreviewPagesCount mod 2) <> 0;
      // Third (and any subsequent) "sub-reports" repeat the pattern
      with frxReportPart3 do begin
        with Variables.Add do begin
          Name := 'BasePageNo';
          if RequiresBlankPage then
            Value := PreviewPagesCount + 1
          else
            Value := PreviewPagesCount;
        end;
        PrepareReport ();
      end;
      // Add empty page if current set of pages (ignoring the Cover Sheet) is an "odd" count
      if RequiresBlankPage then
        frxReportPart3.PreviewPages.AddEmptyPage (0);
      frxReportComposite.PreviewPages.AddFrom (frxReportPart3);
      // Delete the Cover Sheet
      frxReportComposite.PreviewPages.DeletePage (0);
      // now show the report
      frxReportComposite.ShowPreparedReport;
    end; {TFormTestFastReports.ActionReportsCompositeExecute}
    

Leave a Comment

Rich Text Editor. To edit a paragraph's style, hit tab to get to the paragraph menu. From there you will be able to pick one style. Nothing defaults to paragraph. An inline formatting menu will show up when you select text. Hit tab to get into that menu. Some elements, such as rich link embeds, images, loading indicators, and error messages may get inserted into the editor. You may navigate to these using the arrow keys inside of the editor and delete them with the delete or backspace key.