Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performances with FastReport TFrxCrossObject and large Grids (> 1000 rows)

I use FastReport and I need to preview/print Grids with more than 1000 rows and I have some performances problems. Typically I use TfrxCrossObject to prepare my grid because the end user may change the grid presentation (used columns, column's name, size) so I need to have a dynamical print. I tested a simple grid (16 cols x2000 rows) and it needs more than 10 seconds to present the first preview page. Any idea to improve performances ?

EDIT : As said in some answers, the problem is : how to create 'dynamicaly' a grid (with same columns names and sizes that I have on screen) in FastReport without using TFrxCrossObject which seems to be not very efficent. I may admit all solutions like using DataSet or enhancing TfrxCrossObject.

The test code :

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  frxClass, StdCtrls, Grids, frxCross;

type
  TForm1 = class(TForm)
    Button1: TButton;
    StringGrid1: TStringGrid;
    frxCrossObject1: TfrxCrossObject;
    frxReport1: TfrxReport;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure frxReport1BeforePrint(c: TfrxReportComponent);
  end;

var
  Form1: TForm1;

implementation
{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
var
  i, j: Integer;
begin
  for i := 1 to 16 do
    for j := 1 to 2000 do
      StringGrid1.Cells[i - 1, j - 1] := IntToStr(i * j);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  frxReport1.ShowReport;
end;

procedure TForm1.frxReport1BeforePrint(c: TfrxReportComponent);
var
  Cross: TfrxCrossView;
  i, j: Integer;
begin
  if c is TfrxCrossView then
  begin
    Cross := TfrxCrossView(c);
    for i := 1 to 16 do
      for j := 1 to 2000 do
        Cross.AddValue([i], [j], [StringGrid1.Cells[i - 1, j - 1]]);
  end;
end;
end.
like image 883
philnext Avatar asked Mar 10 '12 20:03

philnext


2 Answers

The CrossTab have many overhead. Here is a UserDataSet version :

  1. Just drop 1 stringgrid, 1 button, 1 frxReport, 1 frxUserDataSet in the form.

  2. Set the frxUserDataSet events, Form OnCreate and Buttom OnClick as below code.

  3. No need to Design Report or set any properties, All will be set or generated at run-time.

It seems it is faster than the cross-tab version but you need more coding and lost functionality of CrossObject.

Edit : Add some comments and fix PaperWidth mis-calculation.

Edit2 : Add a print-friendly version which split data into pages.

View Friendly version show in 1 single page as the stringgrid :

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, frxClass, Grids, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    StringGrid1: TStringGrid;
    frxReport1: TfrxReport;
    frxUserDataSet1: TfrxUserDataSet;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure frxUserDataSet1Next(Sender: TObject);
    procedure frxUserDataSet1GetValue(const VarName: string; var Value: Variant);
    procedure frxUserDataSet1CheckEOF(Sender: TObject; var Eof: Boolean);
    procedure frxUserDataSet1First(Sender: TObject);
  private
    X, Y, TCol, TRow : Integer;
    IsEof : Boolean;
    CW, CH, PF : Double;
    Page : TfrxReportPage;
    MDB : TfrxMasterData;
    Memo : TfrxMemoView;
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}


procedure TForm1.Button1Click(Sender: TObject);
var
  BW : Double;
begin
  IsEof := False;
  Page.PaperWidth :=  CW * TCol + 20; // EndlessWidth seems not work with band column
  MDB.SetBounds(0,0, CW * PF * TCol, CH * PF);
  MDB.Columns := TCol;
  MDB.ColumnWidth := CW * PF;
  frxReport1.ShowReport;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  i, j : Integer;
begin
  CW := 12; // Cell Width in mm
  CH := 5;  // Cell Height in mm
  PF := 3.7794; // Pixie Factor i.e. the conversion of mm to FR component measurement
  TCol := 2000; // Total Column
  TRow := 16; // Total Row

  for i := 1 to TRow do
    for j := 1 to TCol do
      StringGrid1.Cells[i - 1, j - 1] := IntToStr(i * j);

  frxUserDataSet1.Fields.Text := 'Data';
  frxReport1.Clear;
  frxReport1.DataSets.Add(frxUserDataSet1);
  Page := TfrxReportPage.Create(frxReport1);
  Page.CreateUniqueName;
  Page.TopMargin := 10;
  Page.BottomMargin := 10;
  Page.LeftMargin := 10;
  Page.RightMargin := 10;
  Page.EndlessHeight := True;
  Page.EndlessWidth := True;
  MDB := TfrxMasterData.Create(Page);
  MDB.DataSet := frxUserDataSet1;
  Memo := TfrxMemoView.Create(MDB);
  Memo.SetBounds(0,0,CW * PF,CH * PF);
  Memo.Memo.Text := '[frxUserDataSet1."Data"]';
  Memo.Frame.Typ := [ftLeft, ftRight, ftTop, ftBottom];
end;

procedure TForm1.frxUserDataSet1CheckEOF(Sender: TObject; var Eof: Boolean);
begin
  Eof := IsEof;
end;

procedure TForm1.frxUserDataSet1First(Sender: TObject);
begin
  X := 0;
  Y := 0;
end;

procedure TForm1.frxUserDataSet1GetValue(const VarName: string; var Value: Variant);
begin
  Value := StringGrid1.Cells[X,Y];
end;

procedure TForm1.frxUserDataSet1Next(Sender: TObject);
begin
  If Y = TCol - 1 then
  begin
    if X = TRow - 1 then
      IsEof := True;
    Inc(X);
    Y := 0;
  end
  else
    Inc(Y);
end;

end.

Print-friendly version which is a bit more complex and separate data in different pages for printing :

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, frxClass, Grids, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    StringGrid1: TStringGrid;
    frxReport1: TfrxReport;
    frxUserDataSet1: TfrxUserDataSet;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure frxUserDataSet1Next(Sender: TObject);
    procedure frxUserDataSet1GetValue(const VarName: string; var Value: Variant);
    procedure frxUserDataSet1CheckEOF(Sender: TObject; var Eof: Boolean);
    procedure frxUserDataSet1First(Sender: TObject);
  private
    X, Y, TCol, TRow, RPP, ColBreak : Integer;
    IsEof : Boolean;
    CW, CH, PF : Double;
    Page : TfrxReportPage;
    MDB : TfrxMasterData;
    Memo : TfrxMemoView;
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses Math;

{$R *.dfm}


procedure TForm1.Button1Click(Sender: TObject);
var
  BW : Double;
begin
  IsEof := False;
  RPP := Ceil((Page.PaperHeight - Page.TopMargin - Page.BottomMargin) / CH) - 1; // Row per page
  ColBreak := RPP; // break to next column

  frxReport1.ShowReport;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  i, j : Integer;
begin
  CW := 12; // Cell Width in mm
  CH := 5;  // Cell Height in mm
  PF := 3.7794; // Pixil Factor i.e. the conversion of mm to FR component measurement
  TCol := 2000; // Total Column
  TRow := 16; // Total Row

  for i := 1 to TRow do
    for j := 1 to TCol do
      StringGrid1.Cells[i - 1, j - 1] := IntToStr(i * j);

  frxUserDataSet1.Fields.Text := 'Data';
  frxReport1.Clear;
  frxReport1.DataSets.Add(frxUserDataSet1);
  Page := TfrxReportPage.Create(frxReport1);
  Page.CreateUniqueName;
  Page.TopMargin := 10;
  Page.BottomMargin := 10;
  Page.LeftMargin := 10;
  Page.RightMargin := 10;
  Page.Columns := Ceil(Page.PaperWidth / CW);
  MDB := TfrxMasterData.Create(Page);
  MDB.DataSet := frxUserDataSet1;
  MDB.SetBounds(0,0, CW * PF, CH * PF);
  Memo := TfrxMemoView.Create(MDB);
  Memo.Align := baClient;
  Memo.Memo.Text := '[frxUserDataSet1."Data"]';
  Memo.Frame.Typ := [ftLeft, ftRight, ftTop, ftBottom];
end;

procedure TForm1.frxUserDataSet1CheckEOF(Sender: TObject; var Eof: Boolean);
begin
  Eof := IsEof;
end;

procedure TForm1.frxUserDataSet1First(Sender: TObject);
begin
  X := 0;
  Y := 0;
end;

procedure TForm1.frxUserDataSet1GetValue(const VarName: string; var Value: Variant);
begin
  Value := StringGrid1.Cells[X,Y];
end;

procedure TForm1.frxUserDataSet1Next(Sender: TObject);
begin
  If X = TRow - 1 then
  begin
    if Y = TCol - 1 then
      IsEof := True
    else
    begin
      frxReport1.Engine.NewColumn;
      Inc(Y);
      X := ColBreak - RPP;
    end;
  end
  else if (X = ColBreak - 1) then
  begin
    if Y = TCol - 1 then
    begin
      frxReport1.Engine.NewPage;
      ColBreak := ColBreak + RPP;
      Y := 0;
    end
    else
      Inc(Y);
    frxReport1.Engine.NewColumn;
    X := ColBreak - RPP;
  end
  else
    Inc(X);
end;

end.
like image 131
Justmade Avatar answered Nov 04 '22 13:11

Justmade


Your piece of code correspond to the PrintStringGrid demo of FastReport slightly modified (RowCount=2000 instead of 16).

Using TStringGrid as a container is not a good idea if you have to deal with large data: It should only be used for presention concern.

Use an in-memory dataset (e.g. ClientDataset as teran suggested in the question comment thread), you can still present your large data to a TStringGrid if using it is compulsory but a TDBGrid is more appropriate.

Iterating a large TDataset is fast than doing the same with a mere TStringGrid.

The PrintTable demo of FastReport could serve as a starting point, adapting it is left to you as an exercise knowing that it uses the same components as PrintStringGrid demo:

  • A TfrxReport and
  • A TfrxCrossObject where the iteration takes place.
like image 36
menjaraz Avatar answered Nov 04 '22 13:11

menjaraz