Печать итоговой суммы по группе в заголовке группы |
Этот довольно часто используемый прием требует использования скрипта. Ведь в обычном отчете значение суммы становится доступным только после того, как будут обработаны все записи группы. Чтобы вывести сумму в заголовке группы (т.е. до того, как будет обработана группа), используется следующий алгоритм: - отчет делается двухпроходным; - на первом проходе считается сумма по каждой группе и сохраняется в каком-нибудь массиве; - на втором проходе значения извлекаются из массива и печатаются в заголовке группы.
Продемонстрируем, как решить эту задачу двумя способами. Для начала создадим новый проект в Delphi, на форму положим компоненты TQuery, TfrxReport, TfrxDBDataSet. Настроим их следующим образом:
Query1: DatabaseName = 'DBDEMOS' SQL = select * from customer, orders where orders.CustNo = customer.CustNo order by customer.CustNo, orders.OrderNo
frxDBDataSet1: DataSet = Query1 UserName = 'Group'
Зайдем в дизайнер и подключим наш источник данных к отчету. В настройках отчета (пункт меню "Отчет|Настройки...") включим двойной проход. Добавим в отчет два бэнда: "Заголовок группы" и "Данные 1 уровня". В редакторе бэнда "Заголовок группы" укажем условие – поле данных Group.CustNo. Дата-бэнд привяжем к источнику данных Group и разместим объекты следующим образом:
Выделенный на рисунке объект (его имя – Memo8) мы используем для вывода суммы.
Способ 1.
Мы используем в качестве массива для хранения сумм класс TStringList. Значения будем хранить в виде строк. При этом первая строка в списке будет соответствовать значению первой группы, и т.д. Для подсчета номера группы будет использована целочисленная переменная, которую мы будем увеличивать после печати очередной группы.
Итак, наш скрипт будет выглядеть следующим образом:
PascalScript:
var List: TStringList; i: Integer;
procedure frxReport1OnStartReport(Sender: TfrxComponent); begin List := TStringList.Create; end;
procedure frxReport1OnStopReport(Sender: TfrxComponent); begin List.Free; end;
procedure Page1OnBeforePrint(Sender: TfrxComponent); begin i := 0; end;
procedure GroupHeader1OnBeforePrint(Sender: TfrxComponent); begin if Engine.FinalPass then Memo8.Text := 'Sum: ' + List[i]; end;
procedure GroupFooter1OnBeforePrint(Sender: TfrxComponent); begin if not Engine.FinalPass then List.Add(FloatToStr(SUM(<Group."ItemsTotal">,MasterData1))); Inc(i); end;
begin
end.
C++ Script:
TStringList List; int i;
void frxReport1OnStartReport(TfrxComponent Sender) { List = TStringList.Create(); }
void frxReport1OnStopReport(TfrxComponent Sender) { List.Free(); }
void Page1OnBeforePrint(TfrxComponent Sender) { i = 0; }
void GroupHeader1OnBeforePrint(TfrxComponent Sender) { if (Engine.FinalPass) Memo8.Text = "Sum: " + List[i]; }
void GroupFooter1OnBeforePrint(TfrxComponent Sender) { List.Add(FloatToStr(SUM(<Group."ItemsTotal">,MasterData1))); i++; }
{
}
По именам процедур можно видеть, какие события мы использовали: Report.OnStartReport, Report.OnStopReport, Page1.OnBeforePrint, GroupHeader1.OnBeforePrint, GroupFooter1.OnBeforePrint. Что касается первых двух событий, то они, как уже говорилось, вызываются в начале и в конце отчета, соответственно. Чтобы создать обработчики для этих событий, надо выделить объект "Отчет" в окне "Дерево отчета" – его свойства появятся в инспекторе объектов. Далее действуем стандартным образом – переключаемся на закладку "События" инспектора и создаем обработчики.
Почему мы не воспользовались для создания списка List главной процедурой, а сделали это в событии OnStartReport? Потому, что созданный объект надо после завершения отчета освободить. Поэтому логично создавать объекты в событии OnStartReport, а освобождать их в OnStopReport. В других случаях (когда не нужно освобождать память) можно пользоваться главной процедурой для инициализации переменных.
С созданием и освобождением объекта List все понятно. Теперь рассмотрим, как работает скрипт. В начале страницы счетчик текущей группы (переменная i) сбрасывается в 0 и увеличивается на единицу после печати каждой группы (в событии GroupFooter1.OnBeforePrint). В этом же событии в список добавляется вычисленное значение суммы. Событие GroupHeader1.OnBeforePrint на первом проходе не срабатывает (проверка Engine.FinalPass). На втором проходе (когда список List заполнен значениями), в этом событии извлекается значение, соответствующее текущей группе, и записывается в текст объекта Memo8, который и показывает сумму в заголовке группы. В готовом отчете это выглядит так:
Как видим, алгоритм достаточно простой. Но и его можно упростить.
Способ 2.
Мы используем в качестве массива для хранения сумм список переменных отчета. Как мы помним, обращение к таким переменным осуществляется с помощью функций Get и Set. Это избавит нас от необходимости создавать лишние объекты и освобождать память. Наш скрипт будет следующим:
PascalScript:
procedure GroupHeader1OnBeforePrint(Sender: TfrxComponent); begin if Engine.FinalPass then Memo8.Text := 'Sum: ' + Get(<Group."CustNo">); end;
procedure GroupFooter1OnBeforePrint(Sender: TfrxComponent); begin Set(<Group."CustNo">, FloatToStr(SUM(<Group."ItemsTotal">,MasterData1))); end;
begin
end.
C++ Script:
void GroupHeader1OnBeforePrint(TfrxComponent Sender) { if (Engine.FinalPass) Memo8.Text = "Sum:" + Get(<Group."CustNo">); }
void GroupFooter1OnBeforePrint(TfrxComponent Sender) { Set(<Group."CustNo">, FloatToStr(SUM(<Group."ItemsTotal">,MasterData1))); }
{
}
Как видно, скрипт значительно упростился. Код в обработчике GroupFooter1.OnBeforePrint устанавливает значение переменной с именем, равным номеру клиента (можно использовать любой идентификатор, однозначно идентифицирующий клиента, например его имя <Group."Company">). Если такой переменной нет – она создается, если есть – меняется ее значение. В обработчике GroupHeader1.OnBeforePrint извлекается значение переменной с номером текущей группы.
|