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.
The CrossTab have many overhead. Here is a UserDataSet version :
Just drop 1 stringgrid, 1 button, 1 frxReport, 1 frxUserDataSet in the form.
Set the frxUserDataSet events, Form OnCreate and Buttom OnClick as below code.
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.
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With