    Отчеты 

    19 ГЛАВА 

    Delphi — не только многофункциональное средство для разработки форм, но и 
очень удобная среда для подготовки отчетов. Входящее в комплект поставки 
Delphi семейство компонентов QuickReport содержит полный набор инструментов, с 
помощью которых можно создавать отчеты практически любой сложности. Кроме 
того, компонент DecisionCube, который также поставляется вместе с Delphi 3.0, 
значительно облегчит подготовку многотабличных отчетов. Если же нужны 
диаграммы, Delphi и тут окажется на высоте. Более того — можно интегрировать 
такие диаграммы с компонентами DecisionCube и получить графическое 
представление данных из нескольких таблиц. Все это можно делать, не выходя из Delphi. 

    Виды отчетов 

    В этой главе будет показано, как с помощью базы данных EMPLOYEE. GDB СУБД 
InterBase, поставляемой в комплекте с Delphi варианта Client/Server, составить 
отчеты четырех видов: простой список потребителей, упорядоченный отчет с 
классификацией клиентов по странам, отчет вида "главный-подчиненный", в 
котором перечислены покупки каждого клиента, и многотабличный отчет со сводной 
информацией о сбыте продукции по всем странам. Кроме того, вы узнаете как в 
отчетах используются диаграммы — мы рассмотрим пример создания круговой 
диаграммы, предназначенной для наглядного отображения показателей объемов 
продаж по странам. 

    База данных EMPLOYEE . GDB 

    База данных EMPLOYEE. GDB входит в состав как Delphi (в варианте 
Client/Server), так и СУБД InterBase. 
    В руководстве по InterBase эта база данных, состоящая из нескольких таблиц 
с достаточно сложными взаимосвязями, используется в качестве примера 
реляционной базы данных. На рис. 19.1 показана схема базы EMPLOYEE.GDB. 
    Кроме того, Delphi по умолчанию поддерживает псевдоним BDE IBLOCAL, 
настроенный для работы с базой данных EMPLOYEE . GDB. Этот псевдоним мы будем 
использовать при разработке отчетов и диаграмм по данным, содержащимся в 
таблицах базы данных EMPLOYEE . GDB. 

    Отчет Customer List Report 

    Чтобы составить табличный отчет о всех клиентах, данные о которых хранятся 
в таблице CUSTOMER базы данных EMPLOYEE . GDB, нужно сделать следующее. 
    1. С помощью команды File^New Application откройте приложение, в котором 
будут храниться отчеты. 
    2. Измените используемое по умолчанию значение свойства Name формы на 
CustomerReport (в результате изменится также и свойство Caption). 

    394 IV. Темы повышенной сложности 

    3. Поместите на стандартную форму компонент TQuickRep и измените значение 
свойства ReportTitle на Customer List Report. 
    4. Поместите на форму компонент TQuery и измените значение свойства Name 
на MasterQuery. 

    На заметку 
    В рассматриваемых здесь методах составления отчетов не используются 
шаблоны QuickReport, имеющиеся в хранилище объектов Delphi. По-моему, они 
несколько неуклюжи. В отличие от других элементов, находящихся на вкладке 
Forms окна Object Repository, элементы QuickReport на самом деле не являются 
формами. Они являются потомками класса TQuickRep. Это означает, что при 
копировании в новый отчет одного из этих шаблонов то, что кажется новой 
формой, на самом деле является чем-то средним между TPanel и TForm с большой 
примесью TQuickRep. В результате формы отчета не появляются в свойстве Forms 
компонента Screen. Это несколько осложняет доступ к ним и приводит к тому, что 
они будут отсутствовать в списке отчетов приложения REPORTS, которое будет 
создано в этой главе. Поэтому, если вы хотите иметь полный доступ к отчету с 
использованием визуального наследования или механизма RTTI (Runtime Type 
Information), лучше создавать отчеты на основе отдельных форм и компонентов TQuickRep. 

    Рис. 19.1. Схема базы данных EMPLOYEE . GDB 

    5. Измените значение свойства DataBaseName компонента MasterQuery на 
IBLOCAL. Введите в качестве значения его свойства SQL следующий оператор. 

    SELECT * FROM CUSTOMER 

    6. Дважды щелкните на свойстве Active компонента MasterQuery, чтобы 
открыть объект запроса (убедитесь, что сервер InterBase доступен и не забудьте 
ввести правильное имя пользователя и пароль). 
    7. Измените значение свойства DataSet компонента QuickRep таким образом, 
чтобы оно ссылалось на компонент MasterQuery. 
    8. Поместите четыре компонента QRBand на компонент QuickRep. Измените 
значение свойства BandType первого из них на rbTitle, второго — на 
rbColumnHeader, третьего — на rbDetaii и четвертого — на rbPageFooter. 

    Глава 19. Отчеты 395 

    9. Поместите компонент QRSy5Data в поле заголовка отчета и измените 
значение его свойства Alignment на taCenter, а свойства Data —на qrsReportTitle. 
    10. Поместите четыре компонента QRDBText в поле данных отчета и измените 
значение их свойств DataSet на MasterQuery. Эти компоненты образуют столбцы 
отчета. Помните, что для выбора сразу нескольких компонентов можно щелкать на 
них левой кнопкой мыши, удерживая нажатой клавишу <Shift>. 
    11. Измените значение свойства DataField первого компонента QRDBText на 
CUSTOMER, второго — на CITY, третьего — на STATE_PROVINCE, и четвертого — на COUNTRY. 
    12. Поместите четыре компонента QRLabel в поле верхнего колонтитула. Они 
будут играть роль заголовков столбцов для перечисленных в отчете полей базы 
данных. Поместите эти компоненты над элементами управления QRDBText и измените 
значения их свойств Caption таким образом, чтобы они описывали соответствующие 
поля таблицы базы данных. Например, свойство Caption первого компонента 
QRLabel должно содержать значение Customer, поскольку в нем будет выводится 
значение поля CUSTOMER. 
    13. Чтобы выделить заголовки столбцов отчета, измените значение свойства 
Font всех четырех компонентов QRLabel таким образом, чтобы в него входили 
атрибуты Bold и Underline. 
    14. Поместите компонент QRLabel в верхний левый угол отчета и в качестве 
значения его свойства Caption укажите Report Date:. 
    15. Разместите под меткой Report Date: еще один компонент QRLabel и в 
качестве значения его свойства Caption укажите Report Time:. 
    16. Поместите компонент QRSysData справа от метки Report Date: и из списка 
возможных значений его свойства Data выберите qrsDate. Компонент QRSysData 
предназначен для возвращения системных переменных, таких как номер текущей 
вкладкы, заголовок отчета и т.п. В данном случае этот компонент используется 
для печати в заголовке отчета текущей даты. 
    17. Поместите второй компонент QRSysData справа от метки Report Date:. Его 
свойство Data по умолчанию имеет значение qrsTime. В результате в заголовке 
отчета будет указано время его печати. 
    18. Сохраните программный модуль новой формы в файле LISTREPO. PAS. 
    Когда эти действия будут выполнены, отчет Customer List Report можно 
считать почти готовым. На рис. 19.2 показано, как должна выглядеть форма 
законченного отчета. 
    Чтобы увидеть, как будет выглядеть отпечатанный отчет, достаточно щелкнуть 
на нем правой кнопкой мыши и выбрать из появившегося контекстного меню команду 
Preview. Отчет, составленный в предыдущем упражнении, должен выглядеть так, 
как показано на рис. 19.3. 
    Теперь, когда вы ознакомились с методами создания простого табличного 
отчета, можно перейти к разработке табличного отчета с разбиением по группам. 
Затем мы разберем ознакомимся с приемами создания отчетов других типов, после 
чего я расскажу вам об особенностях разработки приложений, использующих такие 
отчеты. Закройте окно предварительного просмотра отчета и вернитесь в окно 
конструктора форм Delphi. 

    Рис. 19.2. Так. выглядит готовая форма отчета Customer List Report 

    396 IV. Темы повышенной сложности 

    Рис. 19.3. Предварительный просмотр отчета Customer List Report 

    Отчет Customer Group Report 

    Для того чтобы облегчить дальнейшую работу, давайте сохраним только что 
созданный отчет в хранилище объектов, чтобы впоследствии на его основе 
создавать остальные отчеты. 
    1. Щелкните правой кнопкой мыши на форме отчета Customer List Report и 
выберите из появившегося контекстного меню команду Add To Repository. 
    2. Присвойте сохраняемому элементу название, например Customer Report, и 
измените значение свойства Description на Generic Customer Report Class. 
Выберите для размещения нового элемента хранилища вкладку Forms , после чего 
щелкните на кнопке ОК. 
    3. Выберите команду File^New, а затем на вкладке Forms появившегося 
диалогового окна New Items щелкните на пиктограмме элемента Customer Report. 
    4. Выберите переключатель Inherit, после чего щелкните на кнопке ОК. В 
окне конструктора форм Delphi должна открыться новая форма Customer Report. 
    Обратите внимание, что заголовки, значения свойств DataSet и прочие 
элементы уже настроены в новой форме отчета. Благодаря визуальному 
наследованию, которое поддерживает Delphi, все формы отчетов, создаваемые на 
основании помещенной в хранилище формы Customer Report, настраиваются автоматически. 
    Теперь, когда новая форма появилась на экране, ее можно отредактировать и 
создать отчет с использованием групп Customer Group Report. Для этого нужно 
выполнить следующие действия. 
    1. Измените значение свойства Name новой формы на CustomerGroupReport. 
    2. Измените значение свойства ReportTitle компонента QuickRep на новое 
название, например, Customer Group Report. 
    3. Поместите на форму отчета компонент TQRGroup и присвойте в качестве 
значения его свойства Master ссылку на компонент QuickRep. Измените значение 
свойства Expression на MasterQuery. Country. 
    4. Поместите на компонент TQRGroup компонент TQRExpr и измените значение 
его свойства Expression на MasterQuery.Country. 

    Глава 19. Отчеты 397 

    5. Установите в свойстве Font компонента TQRExpr атрибут Bold и увеличьте 
размер шрифта до 12 пунктов, чтобы выделить в тексте отчета названия стран. 
    6. Если компонент MasterQuery открыт, закройте его (изменив его свойство 
Active на False). 
    7. Отредактируйте его свойство SQL и добавьте фразу ORDER BY Country в 
конец существующего выражения SELECT. В результате это выражение должно 
выглядеть следующим образом. 

    SELECT   *   FROM  CUSTOMER  ORDER  BY   Country 

    8. Откройте компонент MasterQuery, изменив его свойство Active на True. 
    Новый отчет готов. Сохраните программный модуль формы в файле GRPREPO.PAS. 
Для предварительного просмотра отчета щелкните на нем правой кнопкой мыши и из 
появившегося контекстного меню выберите команду Preview (см. рис. 19.4). 

    Рис. 19.4. Предварительный просмотр отчета Customer Group Report 

    Обратите внимание, что сейчас список потребителей упорядочен по странам. 
Такая группировка известна как поуровневая (level-break или control-break). 
Использование групп в отчетах позволяет подводить итоги по каждой группе 
данных отчета. Например, можно присвоить группе нижний колонтитул, 
подсчитывающий количество потребителей в каждой стране. Группы могут иметь как 
верхние, так и нижние колонтитулы, содержащие вычисляемые, системные и обычные поля. 

    На заметку 
    Заметьте, что мы изменили оператор SQL отчета таким образом, что 
возвращаемый им набор данных отсортирован по столбцу country. Если вы 
намереваетесь сгруппировать данные по тому или иному столбцу, сначала 
отсортируйте возвращаемый набор данных. Если этого не сделать, может 
возникнуть ситуация, когда одна группа будет разбита на несколько подгрупп с 
одинаковым названием, размещенных в разных местах отчета. Поэтому при 
использовании групп необходимо удостовериться, что все столбцы таблицы, по 
которым выполняется группировка, сортируются в возвращаемом наборе данных. 

    398 IV. Темы повышенной сложности 

    Отчет вида "главный-подчиненный" 

    Теперь рассмотрим методы создания отчетов вида "главный-подчиненный". С 
помощью таких отчетов можно, например, представить список клиентов с указанием 
всех покупок по каждому клиенту. Для создания такого отчета нужно сделать следующее. 
    1. С помощью команды File^New откройте диалоговое окно New Items, выберите 
на вкладке Forms элемент Customer Report, щелкните на переключателе Inherit, а 
затем на кнопке ОК. В окне конструктора форм Delphi появится новая форма, 
являющаяся потомком формы Customer Report, помещенной в хранилище объектов. 
    2. Измените значение свойства Name новой формы на CustomerMDReport. 
    3. Измените значение свойства ReportTitle компонента Report на Sales By 
Customer Report. 
    4. Поместите на форму компонент TDataSource и укажите в качестве значения 
его свойства DataSet ссылку на компонент MasterQuery. 
    5. Поместите на форму компонент TQuery и измените значение его свойства 
DatabaseName на IBLOCAL. Введите в качестве значения его свойства SQL 
следующий оператор. 

    SELECT * FROM SALES WHERE Cust_No=:Cust_No ORDER BY Order_Date 

    6. В качестве значения свойства DataSource укажите ссылку на уже 
помещенный на форму компонент TDataSource. Это приведет к установлению связи 
вида "главная-подчиненная" между таблицами CUSTOMER, к которой обращается с 
запросом компонент MasterQuery, и таблицей SALES, с которой связан только что 
созданный компонент TQuery. 
    7. Чтобы открыть компонент TQuery, измените его свойство Active на True. 
    8. Поместите на форму отчета компонент TQRSubDetail и укажите в качестве 
значения его свойства Master ссылку на компонент CustomerMDReport. Измените 
значение свойства DataSet таким образом, чтобы оно ссылалось на новый 
компонент TQuery. 
    9. Поместите три компонента TQRLabel и три компонента TQRDBText на 
компонент QRSubDetail. Укажите в качестве значения свойства DataSet всех трех 
компонентов QRDBText ссылку на только что размещенный на форме отчета 
компонент TQuery. 
    10. Измените значение свойства DataField первого компонента QRDBText на 
OrderDate, второго — на Discount, а третьего — на Total_Value. 
    11. Укажите .#0 и #,##0.00 в качестве значения свойства Mask компонентов 
Discount и Total_Value, соответственно. Значение свойства Mask определяет 
формат числа, отображаемыми этими компонентами. В частности, в поле Discount 
должны выводиться значения в формате с плавающей точкой, и маска предназначена 
для ограничения количества значащих цифр. В поле Total_Value должны выводиться 
большие денежные суммы, и он отформатирован соответственно. 

    На заметку 
    Чтобы поля типа DECIMAL и NUMERIC баз данных СУБД InterBase (такие, как 
столбец Total_Value) правильно обрабатывались приложениями Delphi, необходимо 
изменить значение параметра Enable BCD псевдонима IBLOCAL на True. Если этого 
не сделать, BDE и, следовательно, Delphi, будет рассматривать поля этих типов 
как целочисленные. В результате в отчете не будут отображаться десятичные 
точки, и такие компоненты редактирования, как DBEdit, не будут воспринимать их 
при вводе. Подробнее об этом рассказывается в главе 17, "InterBase". 

    12. Поместите над каждым из компонентов QRDBText компоненты QRLabel, 
расположив последние таким образом, чтобы они играли роль заголовков столбцов. 
Настройте свойства Caption компонентов QRLabel так, чтобы они отображали 
назначение соответствующих столбцов. 
    13. Чтобы выделить заголовки столбцов, установите в свойстве Font трех 
компонентов QRLabel, находящихся на компоненте, QRSubDetail атрибут Bold Italic. 

    Глава 19. Отчеты 399 

    Форма нового отчета почти готова. На рис. 19.5 показано, как она должна 
выглядеть в окне конструктора форм Delphi. 
    Сохраните программный модуль формы в файле MDREPO.PAS. Для 
предварительного просмотра отчета щелкните на нем правой кнопкой мыши и из 
появившегося контекстного меню выберите команду Preview (см. рис. 19.6). 

    Рис. 19.5. Форма отчета вида "главный-подчиненный" в окне конструкторе 
форм Delphi 

    Рис. 19.6. Новый отчет вида "главный-подчиненный" 

    400 IV. Темы повышенной сложности 

    Совет 
    Работая над этим разделом, я обнаружил ошибку в компонентах семейства 
QyickReport. Когда я впервые пробовал запустить отчет вида 
"главный-подчиненный", то получил сообщение о~нарушении прав доступа (Access 
Violation). Такое сообщение обычно появляется при ссылке на объект по пустому 
указателю. Вернувшись в окно конструктора форм, я заметил, что ширина 
компонента Order_Date (тип TQRDBText) уменьшилась до нуля. Многократные 
попытки поправить дело оказались тщетными. С этим полем явно было не все в 
порядке. Я убирал и вновь добавлял компонент без каких бы то ни было 
результатов. В конце концов я решил попробовать явно добавить компоненты 
TField в поля запроса, дважды щелкнув на компоненте TQuery, предназначенном 
для работы с таблицей SALES, и нажав <CTRL+A>. Неожиданно это решило все 
проблемы. Если с вами случится то же самое, возможно, мой опыт вам пригодится. 
Если же вы найдете в компонентах QuickReport другие ошибки, можете нанести 
визит на узел Web QuickReports (http://www.qusoft.com). В состав Delphi входит 
также файл QRPTZMAN.DOC (в каталоге Program Files \Delphi 3\Quickrpt), 
содержащий дополнительную информацию об использовании компонентов QuickReport 
в приложениях Delphi. 

    Сводный отчет готов. Закройте окно предварительного просмотра и вернитесь 
в окно конструктора форм Delphi. 

    Многотабличный отчет 

    Из всех рассмотренных в этой главе отчетов многотабличный (cross-tab 
report) является самым необычным, поскольку он не использует компоненты 
семейства QuickReport. Мы рассмотрим пример, в котором они заменены 
компонентами DecisionCube. Это значит, что для вывода отчета на печать вы не 
сможете воспользоваться методами печати компонентов, как в случае с 
компонентами семейства QuickReport. Вместо этого будет применяться метод 
печати формы Print. 
    Для создания многотабличного отчета нужно сделать следующее. 
    1. Откройте новую форму и измените значение ее свойства Name на CustomerCTReport. 
    2. Поместите на новую форму три компонента Т Panel. Измените значение 
свойства Align первого из них на alTop, второго — на alBottom, а третьего — на alClient. 
    3. Удалите свойства Caption второго и третьего компонентов. 
    4. Измените значение свойства Caption первого компонента TPanel на Sales 
By Country and Year, а свойства Font—на Arial 18pt. Regular. 
    5. Поместите компонент DecisionQuery (находящийся на вкладке DecisionCube 
палитры компонентов) и измените значение его свойства DatabaseName на IBLOCAL. 
    6. Поместите на форму компонент DecisionCube и укажите в качестве значения 
его свойства DataSet ссылку на компонент DecisionQuery, который только что был 
помещен на форму отчета. 
    7. Поместите на форму компонент DecisionSource и укажите в качестве 
значения его свойства DecisionCube ссылку на компонент DecisionCube, который 
только что был помещен на форму отчета. 
    8. Поместите компонент DecisionGrid на третий компонент TPanel (он должен 
занимать большую часть формыТюсередине) и измените значение его свойства Align 
на alClient. 
    9. Укажите в качестве значения свойства DataSource компонента DecisionGrid 
ссылку на компонент DecisionSource, помещенный на форму отчета в п. 7. 
    10. Поместите на второй компонент TPanel (он должен располагаться внизу 
формы) компонент DecisionPivot и измените значение его свойства Align на alClient. 
    11. Укажите в качестве значения свойства DataSource компонента 
DecisionPivot ссылку на компонент формы DecisionSource. 
    12. Щелкните правой кнопкой мыши на компоненте DecisionQuery и выберите из 
появившегося контекстного меню команду Decision Query Editor. 
    13. В открывшемся диалоговом окне щелкните на кнопке Query Builder. 

    Глава 19. Отчеты 401 

    14. Откроется диалоговое окно Add Table визуального конструктора запросов 
Visual Query Builder. Добавьте в запрос таблицы CUSTOMER и SALES, дважды 
щелкнув на них, затем щелкните на кнопке Close. 
    15. Перетащите поле Cust_No таблицы CUSTOMER в столбец Cust_No таблицы 
SALES. Между соответствующими полями таблиц установится связь. 
    16. Перетащите поле Country таблицы CUSTOMER на нижнюю панель окна Visual 
Query Builder, чтобы добавить это поле в запрос отчета. 
    17. Добавьте в запрос поля order_Date и Total_Value таблицы SALES, 
перетащив их на нижнюю панель окна Visual Query Builder. Для выхода щелкните 
на кнопке ОК. 
    18. Вернувшись в окно Decision Query Editor, переместите поля Country и 
Order_Date в список Dimension. Переместите столбец Total_Value в список 
Summaries. Когда редактор запросов предложит вам указать способ вычисления 
итогов, выберите функцию sum. В результате в столбце Total_Value будет 
подсчитываться построчная сумма. 
    Когда эти действия будут выполнены, область Query Text на вкладке SQL 
Query должна содержать следующий оператор SQL. 

    SELECT CUSTOMER.COUNTRY, SALES.ORDER_DATE, SUM! 
    FROM CUSTOMER CUSTOMER 
    INNER JOIN SALES SALES 
    ON (CUSTOMER.CUST_NO = SALES.CUST_NO) 
    GROUP BY CUSTOMER.COUNTRY, SALES.ORDER_DATE 
    FROM CUSTOMER CUSTOMER 

    Если оператор отличается, значит, вы сделали что-то неправильно либо в 
окне Visual Query Editor, либо в самом окне Decision Query Editor. 
    19. Если оператор SQL в порядке, закройте диалоговое окно Decision Query 
Editor, щелкнув на кнопке ОК. 
    20. Щелкните правой кнопкой мыши на компоненте DecisionCube и из 
появившегося контекстного меню выберите команду Decision Cube Editor. Щелкните 
на столбце SUM и измените значение свойства Format на #,##0.00. Такое 
форматирование гарантирует правильный вывод итоговой суммы в столбце 
Total_Value. Для выхода из диалогового окна щелкните на кнопке ОК. 
    21. Переключите свойство Active компонента DecisionQuery на True. В 
результате сетка DecisionGrid должна заполниться данными (рис. 19.7). Если 
потребуется ввести имя пользователя и пароль, введите их. 
    22. По окончании работы сохраните программный модуль формы в файле CTREPO. PAS. 

    SALES.TOTAL VALUE 

    Рис. 19.7. Многотабличный отчет в режиме конструктора 

    402 IV. Темы повышенной сложности 

    Разработка приложения 

    Чтобы по достоинству оценить все богатство возможностей компонентов 
семейства DecisionCube при составлении многотабличных отчетов, необходимо 
увидеть эти отчеты в действии. Таким образом, возникает еще одна задача, 
связанная с созданием отчетов — разработка использующих их приложений. В 
листингах 19.1—19.3 содержится исходный текст приложения REPORTS. Это 
приложение обрабатывает все отчеты, составленные в данной главе (для краткости 
их исходный текст опущен) и позволяет просмотреть или запустить их из главной формы. 

    На заметку 
    Для создания приложения REPORTS нужно ввести приведенный ниже исходный 
текст или загрузить его с прилагаемого к книге компакт-диска. 

    Листинг 19.1. Исходный текст файла проекта приложения REPORTS (Reports. DPR) 

    program reports; 
    uses 
    Forms, 
    repoOO in 'repoOO.pas' {Formi}, 
    listrepo in 'listrepo.pas' {CustomerReport}, 
    grprepo in 'grprepo.pas' {CustomerGroupReport}, 
    mdrepo in 'mdrepo.pas' {CustomerMDReport}, 
    ctrepo in 'ctrepo.pas' (CustomerCTReport); 
    ($R *.RES} 
    begin 
    Application.Initializer
    Application.CreateForm(TForml, Formi); 
    Application.CreateForm(TCustomerReport, CustomerReport); 
    Application.CreateForm(TCustomerGroupReport, CustomerGroupReport); 
    Application.CreateForm(TCustomerMDReport, CustomerMDReport); 
    Application.CreateForm(TCustomerCTReport, CustomerCTReport) ; 
    Application.Runt
    end. 

    Листинг 19,2. Исходный текст программного модуля главной формы приложения 
(repoOO. PAS) 

    unit repoOO; 
    interface 
    uses 
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
    StdCtrls, QuickRpt; 
    type 
    TFormI = -class(TForm) 
    btPreview: TButton; 
    btPrint: TButton; 
    ListBoxl: TListBox; 
    btCustomerCTPreview: TButton; 
    btCustomerCTPrint: TButton; 
    Labell: TLabel; 
    Label2: TLabel; 
    procedure btPreviewClick(Sender: TObject); 
    procedure FormShow(Sender: TObject); 
    procedure btPrintClick(Sender: TObject); 
    procedure btCustomerCTPreviewClick(Sender: TObject); 
    procedure btCustomerCTPrint-Click (Sender: TObject); 

    Глава 19. Отчеты 403 

    private 
    { Private declarations } public 
    { Public declarations } end; 
    var 
    Formi: TFormI; 
    implementation uses listrepo, ctrepo; 
    {$R *.DFM) 
    procedure TFormI.btPreviewClick(Sender: TObject); 
    var c,f : Integer; 
    begin 
    if (ListBoxl. Itemlndexo-1) then 
    With Screen do For f:=0 to FormCount - 1 do 
    With Forms[f] fo For c:=0 to ComponentCount - 1 do 
    if (Components[c] is TQuickRep) and 
    (TQuickRep(Components[c])-ReportTitle = 
    ListBoxl.Items[ListBoxl.Itemlndex]) then begin 
    TQuickRep(Components[c]).Preview; 
    break; 
    end; 
    end; 
    procedure TFormI.FormShow(Sender: TObject); 
    var c, f : Integer; 
    begin 
    With Screen do For f:=0 to FormCount - 1 do 
    With Forms[f] do For c:=0 to ComponentCount - 1 do 
    if Components[c] is TQuickRep then 
    ListBoxl.Items.Add( 
    TQuickRep(Components[c]).ReportTitle; 
    If (ListBoxl.Items.Count о 0) then 
    ListBoxl.Itemlndex:=0; 
    end; 
    procedure TFormI.btPrintClick(Sender: TObject); 
    var c,f : Integer; 
    begin if (ListBoxl. Itemlndexo-1) then 
    With Screen do For f:=0 to FormCount - 1 do 
    With Forms[f] fo For c:=0 to ComponentCount - 1 do 
    if (Components[c] is TQuickRep) and 
    (TQuickRep(Components[c]).ReportTitle = 
    ListBoxl.Items[ListBoxl.Itemlndex]) then begin 
    TQuickRep(Components[c]).Print; 
    break; 
    end; 
    end; 
    procedure TFormI.btCustomerCTPreviewClick(Sender: TObject) 
    begin 
    CustomerCTReport.Show; 
    end; 
    procedure TFormI.btCustomerCTPrintClick(Sender: TObject); 
    begin 
    CustomerCTReport.Print; 
    end; 
    end. 

    404 IV. Темы повышенной сложности 

    Листинг 19.3. Текстовое представление главной формы приложения 

    object Formi: TFormI 
    Left = 200 
    Top = 109 
    Width = 544 
    Height = 375 
    Caption = 'Reports' 
    Font.Charset = DEFAULT_CHARSET 
    Font.Color = clWindowText 
    Font.Height = -11 
    Font.Name = 'MS Sans Serif 
    Font.Style = [] 
    OnShow = FormShow 
    PixelsPerInch = 96 
    TextHeight = 13 
    object Labell: TLabel 
    Left = 0 
    Top =40 
    Width =79 
    Height = 13 
    Caption = 'Sales by Country' 
    end 
    object Label2: TLabel 
    Left = 0 
    Top = 120 
    Width = 34 
    Height = 13 
    Caption = 'Others:' 
    end 
    object btPreview: TButton 
    Left = 0 
    Top = 288 
    Width = 75 
    Height = 25 
    Caption = 'Pre&view' 
    TabOrder = 0 
    OnClick = btPreviewClick 
    end 
    object btPrint: TButton 
    Left = 96 
    Top = 288 
    Width = 75 
    Height = 25 
    Caption = 'sprint' 
    TabOrder = 1 
    OnClick = btPrintClick 
    end 
    object ListBoxl: TListBox 
    Left = 0 
    Top = 136 
    Width = 169 
    Height = 137 
    ItemHeight = 13 
    TabOrder = 2 
    end 
    object btCustomerCTPreview: TButton 
    Left = 0 
    Top = 56 
    Width = 75 
    Height = 25 
    Caption = 'Preview' 
    TabOrder = 3 
    OnClick = btCustomerCTPreviewClick 
    end 

    Глава 19. Отчеты 

    object btCustomerCTPrint: TButton 
    Left = 88 
    Top = 56 
    Width = 75 
    Height = 25 
    Caption = 'Print' 
    TabOrder = 4 
    OnClick = btCustomerCTPrintClick 
    end 
    end 

    На рис. 19.8 показано окно работающего приложения REPORTS. 

    Salsa by Country 
    Pieview   |        F'fint 
    Уме. /^.6'. Приложение REPORTS 

    В приложении REPORTS применяется ряд полезных приемов. Рассмотрим исходный 
текст обработчика события OnShow главной формы. 

    procedure TFormI.FormShow(Sender: TObject); 
    var c, f : Integer; 
    begin 
    With Screen do For f:=0 to FormCount - 1 do 
    With Forms[f] do For c:=0 to ComponentCount - 1 do 
    if Components[c] is TQuickRep then 
    ListBoxl.Items.Add( 
    TQuickRep(Components[c]).ReportTitle; 
    If (ListBoxl.Items.Count <> 0) then 
    ListBoxl.Itemlndex:=0; 
    end; 

    В этом фрагменте кода все имеющиеся формы проверяются на наличие в них 
компонентов TQuickRep. 
    Это делается с помощью цикла, в условии которого используется объявленная 
по умолчанию переменная Screen. Ссылки на формы, содержащие компоненты 
TQuickRep, добавляются в компонент ListBoxl. Вспомните, что компонент 
TCustomerReport содержит компонент TQuickRep и является основой остальных форм 
отчетов, рассмотренных в этой главе. 
    Когда список ListBoxl готов, проверяется значение его свойства Count, 
чтобы определить, содержит ли он хоть один компонент TQuickRep. Если — да 
(CountoO), то указатель списка ListBoxl устанавливается на первый из них. 
    В рассматриваемом программном фрагменте есть один интересный момент — 
использование механизма получения информации о типах во время выполнения 
приложения (RTTI — Runtime Type Information) для построения динамического 
списка имеющихся отчетов. Если вы создаете отчеты в виде форм, содержащих 
компоненты TQuickRep, можете использовать этот прием в своих приложениях. 

    406 IV. Темы повышенной сложности 

    Теперь давайте рассмотрим исходный текст обработчика события OnClick 
кнопки Preview. 

    procedure TFormI.btPreviewClick(Sender: TObject); 
    var 
    c,f : Integer; 
    begin 
    if (ListBoxl. Itemlndexo-1) then 
    With Screen do For f:=0 to FormCount - 1 do 
    WitTh"Forms [f] fo For c:=0 to ComponentCount - 1 do 
    if (Components[c] is TQuickRep) and 
    (TQuickRep(Components[c]).ReportTitle = 
    ListBoxl.Items[ListBoxl.Itemlndex]) then begin 
    TQuickRep(Components[c]).Preview; "^^ 
    Break;           .                   ^^^ 
    end;                                       —s^ 
    end;                                                   ' ''^«^ 

    Здесь также просматривается список имеющихся форм и компонентов. Если 
найден отчет, выбранный пользователем из списка ListBoxl, ссылка на компонент 
приводится к типу TQuickRep для вызова метода Preview. Таким образом отчет 
компонент отчета вызывается динамически. Данный подход позволяет подключить 
любой отчет к приложению без написания дополнительного программного кода. 
Ключом к успеху здесь является проверка списков форм и компонентов с помощью RTTI. 

    Просмотр отчетов во время работы приложения 

    Теперь, когда у нас есть удобный интерфейс, воспользуемся им для работы с 
многотабличным отчетом. Щелкните на кнопке Preview, находящейся под меткой 
Sales by Country. Должна появиться таблица, показанная на рис. 19.9. 

    Рис. 19.9. Вид многотабличного отчета во время работы приложения 

    Глава 19. Отчеты 407 

    А сейчас проверим, на что способен компонент DecisionCube. 
    1. Перетащите заголовок столбца Order_Date на заголовок столбца Country. 
Соответствующие строки и столбцы таблицы поменяются местами. Этот прием 
называется сведением (pivoting) и является одной из основных причин 
использования компонентов Deci5ionCube, называемых также сводными таблицами 
(pivot table). 
    2. Щелкните на кнопке Order_Date или Customer, находящиеся на компоненте 
DecisionPivot в нижней части формы. Эти кнопки показывают, какие параметры 
включены в таблицу. В данном случае таких параметров два: Order_Date — по 
вертикали и Customer — по горизонтали. По умолчанию включены все параметры. Но 
в ходе работы над отчетом их можно отключить с помощью элемента управления DecisionPivot. 
    3. Щелкните правой кнопкой мыши на кнопке Order_Date или Customer и из 
появившегося контекстного меню выберите команду Drilled In. Затем щелкните на 
этой же кнопке левой кнопкой мыши. Откроется список возможных значений этого 
параметра. Например, можно ограничить значения в столбце Order_Date 
определенным годом. Можно также выбрать нужные поля столбца Country и 
просмотреть, например, данные по отдельно взятой стране. 

    Графическое представление данных 

    С помощью Delphi можно не только создавать самые замысловатые отчеты, но 
также строить графики и диаграммы. Это делается с помощью компонентов DBChart, 
QRChart и DecisionGraph. 

    QRChart 

    Для начала построим простую диаграмму с помощью компонента QRChart, 
входящего в состав компонентов семейства QuickReport. Поскольку компонент 
QRChart относится к компонентам QuickReport, его можно помещать на формы 
отчетов, а также просматривать и печатать создаваемые им диаграммы с помощью 
таких методов TQuickRep, как Preview и Print. Для создания диаграммы отчета 
QRChart нужно сделать следующее. 
    1. Создайте новую форму, являющуюся потомком формы CustomerReport, и 
присвойте ей имя 
    CustomerGraphReport. 
    2. Щелкните на компоненте TQuickRep и укажите в качестве значения его 
свойства ReportTitle строку Graph of Sales by Country. 
    3. Выделите все четыре компонента QRLabel в области заголовков столбцов 
отчета и удалите значения их свойств Caption. В результате они станут 
невидимыми. Просто удалить их нельзя, поскольку эти компоненты унаследованы от 
другой формы. 
    4. Выделите все четыре компонента QRDBText в области вывода данных отчета, 
очистите значения их свойств DataField и измените значения их свойств Width на 
0. Эти компоненты станут невидимыми. 
    5. Выделите области нижнего колонтитула, вывода данных и верхнего 
колонтитула и измените значение их свойства Enabled на False, а свойства 
Height —на 0. Соответствующие области "исчезнут" из отчета. 
    6. Измените размер области заголовка таким образом, чтобы она занимала как 
минимум половину формы. Здесь разместится диаграмма, так что не жалейте места. 
    7. Поместите компонент QRChart в область заголовка и измените его размер 
таким образом, чтобы он занимал как можно больше места. 
    8. Закройте компонент MasterQuery и измените значение его свойства SQL на следующее. 

    SELECT С.COUNTRY, SUM(S.TOTAL_VALUE) 
    FROM SALES S, 
    CUSTOMER С 
    WHERE S.CUST_NO=C.CUST_NO 
    GROUP BY C.COUNTRY 

    9. Вновь откройте этот компонент, изменив значение его свойства Active на 
True. Этот запрос возвращает список стран из таблицы CUSTOMER с суммарным 
объемом сбыта в них из таблицы SALES. Диаграмма будет построена на основании 
данных этого запроса. 

    408 IV. Темы повышенной сложности 

    10. Дважды щелкните на компоненте QRChart, а затем щелкните на кнопке Add, 
находящейся на вкладке Series появившегося диалогового окна Chart. 
    11. Выберите тип диаграммы Pie, соответствующий круговой диаграмме, и 
щелкните на кнопке ОК. 
    12. Щелкните на вкладке Titles диалогового окна Chart и введите заголовок 
диаграммы: Country Sales Figures. 
    13. Щелкните на вкладке Series (справа от вкладки Chart), затем — на 
вкладке DataSource вкладки Series. 
    14. На вкладке DataSource выберите источник данных диаграммы — из 
раскрывающегося списка DataSet выберите в качестве источника данных компонент MasterQuery. 
    15. Присвойте в качестве значения свойства Labels ссылку на столбец 
Country запроса, а в качестве значения свойства pie — ссылку на столбец SUM. В 
результате размеры секторов диаграммы будут вычисляться на основании 
содержимого столбца SUM, а количество секторов будет равно количеству стран, 
содержащихся в столбце Country. Каждый сектор будет соответствовать объему 
сбыта в одной из стран. 
    Итак, простая круговая диаграмма построена. Щелкните на кнопке Close. 
Готовая диаграмма изображена на рис. 19.10. 

    Рис. 19.10. Круговая диаграмма в окне конструктора форм Delphi 

    Сохраните программный модуль в файле SALEGRAP. PAS. Затем откройте главную 
форму приложения REPORTS и подключите к ней только что созданный программный 
модуль SALEGRAP. Нажмите <F9>, чтобы запустить на выполнение приложение 
REPORTS. Когда откроется окно приложения, выберите из списка отчетов новый 
отчет с диаграммой и щелкните на кнопке Preview (см. рис. 19.11). 
    Налюбовавшись вдоволь своим творением, закройте окна предварительного 
просмотра и самого приложения и вернитесь в Delphi. 

    DecisionGraph 

    Для создания диаграммы на основе данных, получаемых их сводной таблицы 
компонента DecisionCube, можно использовать компонент DecisionGraph. Для этого 
добавим DecisionGraph в созданную ранее форму CustomerCTReport. Как вы 
помните, на этой форме мы уже разместили компонент DecisionCube. 
    Чтобы добавить новую диаграмму к старой форме CustomerCTReport, нужно 
сделать следующее. 
    1. Нажмите <Shift+F12> и выберите из списка форму CustomerCTReport. 
    2. Измените свойство Align компонента DecisionGrid на alTop и поднимите 
его нижнюю границу таким образом, чтобы компонент занимал около половины 
свободного места в центре формы. 
    3. Поместите компонент DecisionGraph на место, освобожденное компонентом 
DecisionGrid, и измените значение его свойства Align на alClient. Этот 
компонент должен занять оставшееся место на форме. 
    4. Укажите в качестве значения его свойства DecisionSource ссылку на 
компонент формы DecisionSource. На экране должна появиться гистограмма. 

    Глава 19. Отчеты 409 

    5.   Чтобы отредактировать новую диаграмму, дважды щелкните на ней. 
Щелкните на вкладке Titles, укажите в строке ввода Title заголовок Sales by 
Country and Year, затем щелкните на кнопке Close. Диаграмма готова. По оси X 
откладываются значения столбцов сводной таблицы DecisionCube, а по оси Y — 
значения поля зим. Теперь посмотрим, как работает новая диаграмма. Сохраните 
проект, запустите приложение REPORTS и щелкните на кнопке Preview, 
предназначенной для открытия отчета Sales by Country. Должно появиться 
изображение, показанное на рис. 19.12. 

    Рис. 19.11. Круговая диаграмма в окне предварительного просмотра 

    Puc. 19.12. Так выглядит диаграмма, созданная на основе компонента DecisionGraph 

    410 IV. Темы повышенной сложности 

    Чтобы увидеть, насколько тесно связаны компоненты DecisionGrid и 
DecisionGraph, поместите столбец Order_Date на столбец Country в компоненте 
DecisionPivot. Столбцы поменяются местами (как показывает указатель мыши), в 
результате чего таблица "перевернется". Обратите внимание, что при сведении 
DecisionCube изменяются и таблица, и диаграмма (рис. 19.13). 
    Диаграмма закончена. Выйдите из приложения REPORTS и вернитесь в Delphi. 

    Рис. 19.13. С помощью компонента DecisionPivot можно управлять 
компонентами DecisionGrid и DecisionGraph одновременно 


    Совершенствование отчетов 

    Поскольку мы с самого начала благоразумно составляли большинство отчетов 
на основе единого базового отчета, их совершенствование не составит труда. 
Воспользуемся тем, что все отчеты унаследованы от класса CustomerReport, и 
добавим в область заголовка каждого отчета имя пользователя. 
    Чтобы добавить в отчет поле имени пользователя, выполните следующее. 
    1.    Откройте в окне конструктора форм Delphi отчет CustomerReport. 
    2.    Поместите справа от области заголовка на уровне области даты печати 
отчета два компонента QRLabel. Присвойте первому из них имя UserNameLabel, а 
второму — laUserName. Поскольку эти компоненты будут унаследованы другими 
формами, их имена должны быть уникальными среди всех форм иерархии. Имена, 
присваиваемые Delphi по умолчанию одной из форм, могут вызвать конфликт с 
именами другой формы. 
    3.    Расположите элементы управления один за другим, таким образом, чтобы 
совместить крайний правый с правым полем отчета. 
    4.    Измените значение свойства Caption компонента UserNameLabel на 
UserName:, а свойства Allignment компонента laUserName — на taRightJustify. 
    5.   Дважды щелкните на событии OnPrint компонента laUserName и измените 
исходный текст обработчика данного события следующим образом. 

    procedure  TCustomerReport.laUserNamePrint(Sender:   TObject;   var  
Value:   String); var 
    MaxNameLen   :   Integer; begin 
    MaxNameLen   :=   30; 
    SetLength(Value,MaxNameLen); 
    GetUserName(PChar(Value),MaxNameLen); 
    SetLength(Value,Pred(MaxNameLen)); 

    Глава 19. Отчеты 411 

    //Имя передается  в  виде  строки  с  завершающим нулем. end; 
    Для присвоения значения параметру Value данная процедура вызывает функцию 
GetUserNarae API Windows. Содержимое Value будет напечатано в отчете. Обратите 
внимание на преобразование строки Value в строку типа PChar при передаче ее в 
качестве параметра процедуре GetUserName. Затем, прежде чем закончить 
обработку события, определяется длина Value. Это делается с помощью переменной 
MaxNameLen (в которую процедура GetUserName занесла количество байтов, 
скопированных в Value). Такая предосторожность гарантирует корректное 
окончание строки. 
    Чтобы увидеть результат внесения изменений, вновь запустите приложение 
REPORTS и посмотрите, как выглядят ранее созданные отчеты. Вы заметите, что в 
правом верхнем углу всех отчетов, за исключением многотабличного, появилось 
поле имени пользователя. Это поле было добавлено только в базовый отчет, но 
оказалось унаследованным всеми производными формами. Представьте, сколько 
усилий можно было бы сэкономить, если бы разработчики приложений Delphi 
проектировали системы, полностью используя это мощное средство. На рис. 19.14 
показан отчет Customer List Report с новым полем. 
    Рис. 19.14. Наследование форм позволяет сделать широкомасштабные изменения 
за несколько секунд 

    Резюме 

    Теперь вы знаете, как создавать отчеты различных типов с помощью 
компонентов семейства QuickReport: простой табличный отчет-список, 
упорядоченный отчет, многотабличный отчет и отчет вида "главный-подчиненный". 
Вы научились также строить объемные круговые диаграммы и гистограммы, 
основанные на компоненте DecisionCube. Я думаю, что вы по достоинству оценили 
значение визуального наследования форм при построении форм отчетов, и узнали, 
как вносить глобальные изменения в иерархию отчетов. Вы построили также 
изящное приложение-интерфейс для тестирования отчетов. Рассмотренный в этой 
главе широкий спектр вопросов, связанных с отчетами, послужит вам надежным 
подспорьем при создании в Delphi собственных отчетов баз данных архитектуры клиент/сервер. 

    412 IV. Темы повышенной сложности 


    Что дальше... 

    В главе 20, "Бизнес-правила и сервер базы данных", рассматривается 
множество способов реализации бизнес-правил на сервере, дополняющих созданные 
вами приложения Delphi. Вы узнаете много полезного бизнес-правилах серверной 
части приложения и научитесь конструировать триггеры, ограничения, 
представления и хранимые процедуры, реализующие эти правила. 

    Глава 19. Отчеты	413