Отчёты с деревьями
© 2010 Денис Зубов
ведущий разработчик FastReports
На сегодняшний день трудно найти компанию, которая не использует достижения информационных технологий для управления и автоматизации производственным процессом. Различные системы способны упростить и автоматизировать значительную часть функций компании. Одной из важнейших задач таких систем является систематизация данных и создание отчетности на основе собранной информации("Генераторы Отчетов").
Отчеты и разные документы являются неотъемлемой частью любой компании, различаются лишь формы этих документов. Но не всегда данные имеют "плоский" вид, некоторые задачи требуют построение иерархии или древовидных структур. Большинство генераторов отчетов работают именно с плоскими наборами данных или таблицами.
Далее будут описаны несколько вариантов решения проблемы печати иерархии средством генератора отчетов "Fast Report".
В качестве самого простого примера можно рассмотреть пример иерархии сотрудников фирмы, т.е. есть руководители, и есть подчиненные (в больших компаниях такая иерархия может быть глубокой). Данные можно представить в виде плоской таблицы, одно из полей которых ссылается на вышестоящего сотрудника (руководителя) тем самым образуется иерархия.
Пример такой таблицы приведен на рисунке (EmpNo – уникальный номер сотрудника, EmpOwner – указывает у кого в подчинении находится данный сотрудник).

Идея создания отчета проста, необходимо отсортировать данные в соответствии с номером сотрудника и номером руководителя. Т.е. в начале будут идти сотрудники без руководителей (пустое поле), далее за каждым руководителей должны идти подчиненные и так по всей цепочке для каждой записи. Для сортировки набора данных можно воспользоваться списком, в который будут заноситься номера сотрудников, и сортироваться в нужном порядке. Для перехода к нужной записи набора данных будем использовать функцию Locate.
Перейдем от описания непосредственно к созданию отчета. Создайте новый шаблон и установите подключение к базе данных, добавьте таблицу или запрос для выборки необходимых данных. Пример такой таблицы приложен к статье вместе с примером(HDemo.mdb). Добавьте в отчет бенды : "Заголовок отчета", "Данные первого уровня" и "Подвал страницы". Разместите на заголовке отчета объект "текст" с произвольным текстом (к примеру "Сотрудники"), а на подвале страницы объект "текст" с переменной [Page]. В нашем отчете данные бэнды несут декоративный характер для придания вида отчету, основная же обработка будет связана с бэндом данных. Шаблон отчета должен получиться приблизительно как на рисунке ниже.

Добавьте нужные поля из набора данных (перетащите из дерева данных) на бэнд данных, но не привязывайте бенд к данным. В итоге конечный вид шаблона будет иметь приблизительно такой вид:

Для обработки, выбора данных и смещения будем использовать скрипт отчета. Для этого добавьте следующие события у перечисленных объектов(события можно добавить через инспектор объектов):
- "Данные первого уровня"(MasterData1) – события: OnAfterPrint и OnBeforePrint.
- "Отчет"(Report – можно выбрать в списке инспектора объектов), событие OnStopReport.
На этом подготовка шаблона закончена и можно приступать непосредственно к реализации сортировки и вывода через скрипт отчета. Для этого переключитесь на вкладку "код" и приступим к написанию скрипта. В самом верху скрипта объявим глобальные переменные:
varПерейдем к главной процедуре скрипта (код между begin … end.), в ней будут инициализироваться переменные и вызываться заполнение списка сортировки.
TreeList: TStringList;// список сортировки
ShiftList: TStringList;// список сортировки
FDBdataSet: TfrxDBDataSet;// набор данных
FVal: variant;// Временная переменная для хранения значений
FPrevEmpNo: Integer;// переменная для хранения предыдущего номера
oldCurX: Extended; // переменная для хранения оригинальной позиции вывода бэнда
begin
{ получаем набор данных }
FDBdataSet := TfrxDBDataSet(Report.GetDataset('ADOTable1'));
{ создание списка сортировки }
TreeList := TStringList.Create;
{ создание списка смещения }
ShiftList := TStringList.Create;
if FDBdataSet = nil then Exit;
{ открываем набор данных, и переходим на первую запись}
FDBdataSet.Open;
FDBdataSet.First;
{ цикл по всем записям набора данных }
while not FDBdataSet.Eof do
begin
{ получаем номер текущего сотркудника }
FPrevEmpNo := FDBdataSet.Value('EmpNo');
AddEmp;
{ восстанавливаем курсок набора данных после выполнения AddEmp }
FDBdataSet.Locate('EmpNo', FPrevEmpNo, 0);
{ переход к следующей записи }
FDBdataSet.Next;
end;
MasterData1.RowCount := TreeList.Count;
end.
Функция Report.GetDataset возвращает объект набора данных по его имени(если он найден). Далее идет создание списков сортировки, смещения и открытие набора данных, после чего организован цикл по всем записям набора данных, в котором вызывается функция добавления значения в список AddEmp (описана ниже). Функция FDBdataSet.Locate устанавливает курсор в наборе данных в позицию, где значение указанного поля совпадает со значением, переданным в качестве второго параметра функции. MasterData1.RowCount задает кол-во повторений бэнда данных (кол-во строк).
Функция сортировки должна быть объявлена перед главной процедурой, ее реализация приведена ниже:
{ добавление номера сотрудника в позицию соответствующей ему иерархии } function AddEmp: Integer;
var
FEmpNo: Integer;
RNo: Integer;
s: String;
begin
FVal := FDBdataSet.Value('EmpOwner');// номер руководителя
FEmpNo := FDBdataSet.Value('EmpNo');// номер текущего сотрудника
s := IntToStr(FEmpNo);
Result := TreeList.IndexOf(s);// поиск сотрудника в списке, если найден не добавляем
if Result <> -1 then
exit;
if FVal <> NULL then
begin
{ сотрудник имеет руководителя }
{ переход к руководителю }
FDBdataSet.DataSet.Locate('EmpNo', FVal, 0);
{ добавляем сотрудника после руководителя }
Result := AddEmp;
{ добавляем сотрудника после руководителя }
Inc(Result);
if Result >= TreeList.Count then
TreeList.Add(s)
else
TreeList.Insert(Result, s);
end
else begin
{ сотрудник не имеет руководителей(как же ему повезло !), просто добавляем его в список}
TreeList.Add(s);
Result := TreeList.Count;
end;
end;
Функция добавляет сотрудника следом за его руководителем, если руководителя еще нет в списке, производится рекурсивный вызов и так до полного заполнения цепочки. Если сотрудник уже был добавлен вследствие рекурсивного вызова, происходить выход из функции и переход к следующей записи (в главной процедуре).
В событии OnStopReport удаляем созданные списки.
procedure ReportOnStopReport(Sender: TfrxComponent);
begin
{ удаление списков }
TreeList.Free;
ShiftList.Free;
end;
Выборка данных перед выводом в соответствии с сортировкой и смещение бэнда реализовано в событии MasterData1OnBeforePrint. Переход к нужной записи происходит с помощью уже знакомой функции Locate, в качестве ключа поиска используются значения из списка.
procedure MasterData1OnBeforePrint(Sender: TfrxComponent);В событии MasterData1OnAfterPrint восстанавливаем позицию после смещения.
var
eOwner: String;
i: Integer;
begin
{ перехватываем добавление страницы, чтобы CurX не обнулилась}
if MasterData1.Height > Engine.FreeSpace then
Engine.NewPage;
FPrevEmpNo :=;
{ установка позиции в наборе данных }
FDBdataSet.DataSet.Locate('EmpNo', TreeList[- 1], 0);
{ получаем текущего руководителя }
eOwner := IntToStr();
{ если руководитель не найден добавляем его }
{ иначе получаем индекс смещения для сотрудника }
i := ShiftList.IndexOf(eOwner);
if i = -1 then
begin
{ для корректного смещения узла проверяем предыдущий индекс }
i := ShiftList.IndexOf(IntToStr(FPrevEmpNo)) + 1;
{ если предыдущий ключ последний в списке, то добавляем новое смещение }
if i >= ShiftList.Count then
begin
ShiftList.Add(eOwner);
i := ShiftList.Count - 1;
end
{ иначе заменяем ключ поиска }
else
ShiftList[i] := eOwner;
end;
{ запоминаем текущую позицию бэнда для восстановления }
oldCurX := EnginE.CurX;
{ смещаем бэнд }
EnginE.CurX := EnginE.CurX + 20 * i;
end;
procedure MasterData1OnAfterPrint(Sender: TfrxComponent);
begin
{ восстанавливаем позицию бэнда }
EnginE.CurX := oldCurX;
end;
Отчет готов:

В заключении можно отметить, что использование Locate на больших наборах данных может снизить производительность построения отчета, в этом случае можно хранить все необходимые данные в списке, либо в древовидной структуре. Без использования Locate при выборке данных. Общий принцип построения остается прежним.
При наличии древовидных структур в вывод значительно упрощается, т.к. не нужно сортировать данные, и можно сразу перемещаясь по дереву в событии OnManualBuild выводить узлы на бэнде через Engine.ShowBand, но это уже тема для отдельной статьи.
Пример отчета и базы может быть запущен как из "Fast Report 4 VCL" , так и из "Fast Report Studio".
Copyright© 2010 Денис Зубов, ведущий разработчик FastReports
| 2011 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 2010 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 2009 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 2008 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 2007 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 2006 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 2005 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 2004 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 2003 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 2002 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 2001 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 2000 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 1999 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
- Услуги аутсорсинга в области программирования
- Как продлить срок службы картриджей
- Мошенничество во Всемирной Паутине. Осторожно: фишинг!
- Web-студия
- Как легально поднять уровень индекса цитирования.
- Мы реально сможем помочь вам в управлении предприятием
- Создание сайтов – популяризация вашего замысла
- Свой сайт. Управление ресурсом
- Семантическое ядро сайта или правила подбора ключевых фраз
- Инфо-Предприятие: выгоды явные и не явные
- Программирование в среде Delphi 8 for .NET
- Практикум по Delphi для решения прикладных задач
- Фундаментальные алгоритмы и структуры данных в Delphi
- Delphi 6. Программирование на Object Pascal
- Delphi и технология COM
- Delphi в шутку и всерьез: что умеют хакеры
- Программирование в Delphi глазами хакера
- Delphi 2005. Секреты программирования
- Искусство создания компонентов Delphi
- Приемы программирования в Delphi на основе VCL
- Программирование баз данных в Delphi 7
- Программирование баз данных в Delphi
- Программирование в среде Delphi
- Программирование в Delphi 7
- Язык SQL в Delphi 5