Производительность с FastReport TFrxCrossObject и большими сетками (> 1000 строк) - PullRequest
6 голосов
/ 11 марта 2012

Я использую FastReport, и мне нужно предварительно просмотреть / распечатать таблицы с более чем 1000 строками, и у меня возникли проблемы с производительностью. Обычно я использую TfrxCrossObject для подготовки своей сетки, потому что конечный пользователь может изменить представление сетки (используемые столбцы, имя столбца, размер), поэтому мне нужно иметь динамическую печать. Я протестировал простую сетку (16 столбцов х 2000 строк), и для представления первой страницы предварительного просмотра требуется более 10 секунд. Есть идеи улучшить выступления?

РЕДАКТИРОВАТЬ: Как говорилось в некоторых ответах, проблема заключается в том, как создать «динамически» сетку (с теми же именами и размерами столбцов, что и у меня на экране) в FastReport без использования TFrxCrossObject, что, по-видимому, не очень эффективно. Я могу допустить все решения, такие как использование DataSet или расширение TfrxCrossObject.

Тестовый код:

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.

Ответы [ 2 ]

4 голосов
/ 13 марта 2012

У CrossTab много накладных расходов. Вот версия UserDataSet:

  1. Просто перетащите 1 сетку строк, 1 кнопку, 1 frxReport, 1 frxUserDataSet в форме.

  2. Установите события frxUserDataSet, Form OnCreate и Buttom OnClick, как показано ниже.

  3. Нет необходимости разрабатывать отчет или задавать какие-либо свойства. Все будет установлено или сгенерировано во время выполнения.

Кажется, это быстрее, чем кросс-таблица, но вам нужно больше кода и потерянная функциональность CrossObject.

Редактировать: добавить несколько комментариев и исправить неверный расчет PaperWidth.

Edit2: добавить версию для печати, которая разбивает данные на страницы.

Просмотр дружественной версии в виде одной строки на одной странице:

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.

Версия для печати, которая немного более сложна и содержит отдельные данные на разных страницах для печати:

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.
2 голосов
/ 14 марта 2012

Ваш кусок кода соответствует PrintStringGrid демонстрации FastReport, слегка измененной (RowCount = 2000 вместо 16).

Использование TStringGrid в качестве контейнера не является хорошей идеей, если вам приходится иметь дело с большимиданные: он должен использоваться только для представления.

Используйте набор данных в памяти (например, ClientDataset, как предложено teran в ветке комментариев к вопросу), вы все равно можете представить свои большие данные в TStringGrid, если вы используете егообязательно, но TDBGrid более уместен.

Итерация большого TDataset выполняется быстрее, чем делать то же самое с простым TStringGrid.

Демо PrintTable FastReport может служить отправной точкой, адаптируяон оставлен вам как упражнение, зная, что он использует те же компоненты, что и PrintStringGrid demo:

  • A TfrxReport и
  • TfrxCrossObject, где итерация происходит.
...