    Разработка отчетов 

    12 ГЛАВА 

    В этой главе мы продолжим этап разработки проекта RENTMAN и приступим к 
созданию отчетов приложения. Мы создадим и подключим к приложению три 
следующих отчета. 
    Q Форма-отчет наряда на выполнение работ. 
    Q Табличный отчет по таблице PROPERTY. 
    Q Табличный отчет нарядов по каждому рабочему. 

    Методы создания отчетов в Delphi 

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

    На заметку 

    Вы, наверное, можете сказать, что существует и четвертый метод получения 
отчетов, — с использованием коммерческих конструкторов отчетов, таких как 
Crystal Reports, R&R и ReportSmith. Я не включил их в приведенный выше 
перечень, потому что такие системы не имеют прямого отношения к Delphi. Если 
хотите, можете попробовать поработать с ними, но помните, что ни в одном 
варианте Delphi 3 в комплект поставки уже не включается конструктор отчетов 
ReportSmith, как это было в предыдущих версиях, поэтому вам придется 
самостоятельно приобрести его и установить как отдельное приложение. Однако 
смею вас заверить, что средства создания отчетов компании Borland, 
интегрированные в саму систему Delphi, настолько хороши, что вам не 
понадобятся внешние конструкторы отчетов. Более того, я могу со всей 
ответственностью утверждать, что на основе компонентов QuickReport можно 
создать отчеты, которые будут так же удобны в работе, как и отчеты, созданные 
с помощью ReportSmith, но при этом на их создание у вас уйдет гораздо меньше 
времени и сил, чем при использовании ReportSmith. 

    Типы отчетов 

    Отчеты, используемые в делопроизводстве, делятся на четыре основных типа: 
формы, этикетки, табличные отчеты и многотабличные отчеты. Формы-отчеты (form 
report) содержат на каждой странице только одну запись таблицы базы данных или 
одну запись из главной таблицы и соответствующие ей записи из подчиненной 
таблицы. Хорошим примером такого типа отчетов является счет-фактура — на 
каждой странице такого отчета расположен только один счет. 


    Глава 12. Разработка отчетов 259 

    Отчеты-этикетки (label report) — это именно этикетки, т.е. данные такого 
отчета предназначены для изготовления наклеиваемых этикеток для почтовой 
рассылки. При подготовки такого отчета выбирается тип этикеток и их количество 
на каждой странице отчета. Обычно такие отчеты печатаются на специальной 
бумаге с уже готовыми самоклеящимися этикетками. Примером отчета такого типа 
является отчет, подготовленный по данным таблицы CUSTOMER, для рассылки счетов 
клиентам, взявшим товары в кредит. 
    Табличный отчет (columnar report) — это отчет в традиционном смысле этого 
слова. Данные в отчетах такого типа представлены в виде столбцов. Как правило 
в табличных отчетах на каждой странице располагается много строк. В табличных 
отчетах могут выделяться группы данных с разным форматированием и подведением 
итогов. Типичным примером табличного отчета является список клиентов компании. 
    Многотабличный отчет (cross-tab report) имеет вид, напоминающий вид листа 
электронной таблицы. В отличие от табличного отчета, данные, содержащиеся в 
многотабличном отчете, не только организованы в столбцы, но и отсортированы 
таким образом, чтобы в расположении данных, находящихся на пересечении строк и 
столбцов отчета, можно было проследить определенную закономерность, как это 
обычно делается в электронных таблицах. Хорошим примером многотабличного 
отчета является отчет о ежеквартальных объемах продажи по штатам. В левом 
столбце такого отчета перечислены штаты, а в каждой соответствующей строке — 
данные о ежеквартальной продаже по данному штату. Если вам нужен показатель 
продажи, например, по второму кварталу для какого-то конкретного штата, вы 
будете искать нужное значение на пересечении строки, представляющей штат, и 
столбца, представляющего объем продажи во втором квартале. 
    В этой книге вы ознакомитесь со всеми этими типами отчетов. Первый отчет, 
который мы разработаем, — отчет Work Order — является формой-отчетом. Для 
создания этого отчета достаточно распечатать саму форму fmRWORMDEO. В 
соответствующем разделе данной главы вы узнаете, как создавать отчеты такого 
типа, используя только формы и стандартные компоненты Delphi. 
    С созданием табличных отчетов вы ознакомитесь, когда разработаете отчет 
такого типа для таблицы PROPERTY. Этот отчет основан на использовании 
компонентов QuickReport. Как вы убедитесь на собственном опыте, такой метод 
разработки отчетов является самым удобным и подходит для создания подавляющего 
большинства отчетов. 
    В главе 19, "Отчеты", вы узнаете, как с помощью компонента Delphi 
DecisionCube создать многотабличный отчет. 
    Пример отчета для создания этикеток, основанный на использовании 
компонентов QuickReport, можно найти в хранилище объектов Delphi. Выберите 
команду File=>New и на вкладке Forms открывшегося диалогового окна выберите 
соответствующий элемент хранилища объектов. 
    Данная глава заканчивается разделом, посвященным созданию отчета только на 
основе операторов языка Object Pascal. Метод создания отчетов путем применения 
циклов и операторов форматированного вывода уже устарел и приводится здесь 
только для ознакомления. 

    Отчет-форма Work Order 

    Итак, давайте начнем с отчета-формы Work Order. Этот отчет будет создан на 
основе формы заполнения наряда — формы fmRWORMDEO. 
    Для начала откройте главную форму системы RENTMAN, fmRSYSMANO, в окне 
конструктора форм Delphi. Поместите на нее компонент printerSetupDialog и 
назовите объект ps_RENTMAN. Щелкните дважды на пункте Print подменю File 
главной формы и введите следующий оператор: 

    ps_RENTMAN.Execute; 

    Вернитесь в окно конструктора форм и свяжите кнопку sbPrintSetup с 
обработчиком события OnClick пункта Print меню приложения (этот обработчик 
события должен называться PrintsetuplClick). Это обеспечит открытие 
диалогового окна установки параметров печати при щелчке на кнопке PrintSetup 
панели инструментов приложения RENTMAN. 
    Кроме прочих параметров, диалоговое окно Print Setup (Печать) позволяет 
выбирать принтер, на который будет направлено задание печати. Все приложения 
для Windows должны предоставлять пользователю возможность открытия такого 
диалогового окна. 

    Настройка пункта меню для печати наряда 

    Теперь нужно связать пункт Work Order подменю Reports с пунктом Work 
Orders подменю Tables. Поскольку форма заполнения наряда будет распечатывать 
сама себя, то все, что вам нужно сделать, чтобы предоставить пользователю 
возможность напечатать отчет по наряду, — открыть форму выбора наряда, 
fmRWORGRDO. Поскольку эта форма отображается при выборе пользователем команды 
Tables=>Work Orders, то, естественно, надо связать с ней команду Reports=>Work Order. 


    260 Часть II. Учебный курс 

    Кнопка Print 

    Откройте в окне конструктора форм форму fmRWORMDEO и поместите на ее 
панель paBottom слева от кнопки ОК компонент BitBtn. Измените имя нового 
объекта на bbPrint, в качестве значения свойства Caption укажите sprint, а 
свойству Glyph присвойте значение \lMAGES\BUTTONS\print.bmp (каталог Images по 
умолчанию располагается в главном каталоге системы Delphi). Проделав все это, 
щелкните дважды на кнопке — должно открыться окно редактирования исходного 
текста для ввода операторов обработчика события OnClick кнопки. Введите 
операторы, представленные в листинге 12.1. 

    ЛИСТИНГ 12.1. Печать формы fmRWORMDEO 

    Tag := Longint(WindowState); 
    WindowState:=wsMaximized; 
    for 1:=0 to ComponentCount-1 do 
       If (Components[I] is TButton) or 
          (Components[I] is TDBNavigator) or 
          (Components[I] is TSpeedButton) then 
              With Components[I] as TControl do begin 
                 Tag:=Longint(Visible) ; 
                 Visible:=False; 
            end; 
    Print; 
    for I:=0 to ComponentCount-1 do 
        If (Components[I] is TButton) or 
           (Components[I] is TDBNavigator) or 
           (Components[I] is TSpeedButton) then 
              With Components[I] as TControl do 
                 Visible:=Boolean(Tag) ; 
   WindowState :=TWindowState(Tag) ; 

    He забудьте объявить идентификатор I как переменную типа Integer в 
заголовке процедуры, например: 

    procedure TfmRWORMDEO .bbPrintdick (Sender: TObject) ; 
    var 
    I: Integer; 

    Приведенные выше операторы выполняют три основные задачи. Во-первых, они 
скрывают те компоненты, которых не должно быть в напечатанном отчете. 
Например, в бумажной копии формы вам вряд ли понадобиться изображение кнопок. 
Во-вторых, с помощью вызова метода Print печатается сама форма. Вызов этого 
метода приводит к тому, что форма в том виде, в котором она отображается на 
экране, распечатывается на принтере. Наконец третья задача — отображение всех 
ранее скрытых компонентов. 
    Хочу остановиться на некоторых достаточно важных моментах в организации 
данного обработчика события. Прежде всего, обратите внимание на интенсивное 
использование свойства Tag. Это свойство имеет тип Longint и включается в 
каждый компонент Delphi без какой-либо определенной цели. Поэтому данное 
свойство удобно использовать для хранения разного рода промежуточных и 
вспомогательных значений. В рассматриваемом примере свойство Tag используется 
на двух уровнях. Свойство Tag формы применяется для временного хранения 
значения свойства WindowsState, а свойство Tag компонентов формы — для 
временного хранения значений их свойств Visible. Если вы не примените такой 
подход, заключающийся в сохранении значений свойств с последующим их 
восстановлением, то вам придется основываться на каких-то предположениях, 
которые могут оказаться неверными. Например, вы не можете знать наверняка, 
имеет ли свойство формы WindowState значение wsNormal. Точно так же, нельзя 
точно знать, все ли компоненты формы являются видимыми, так как в каких-то 
случаях вы можете скрыть те или иные компоненты. Если вы исходите из 
предположения, что все компоненты изначально являются видимыми, то после 
печати формы и возвращения всех компонентов в видимое состояние те из них, 
которые должны быть скрытыми, также появятся на экране. Таким образом, данный 
обработчик события демонстрирует более правильный и универсальный подход. 
    Еще одним важным аспектом данного программного фрагмента является 
использование свойства Components. Данное свойство представляет собой 
свойство-массив компонентов, которым обычно обладают такие 
компоненты-контейнеры, как TForm. В рассматриваемом примере это свойство 
применяется для перебора всех компонентов формы с целью определения 
необходимости печати в отчете каждого конкретного компонента. В зависимости от 
полученного результата изменяется значение свойства Visible каждого компонента 
формы. Таким образом, с помощью свойства Components можно во время работы 
приложения быстро получить доступ к компонентам, помещенным в компонент-контейнер. 


    Глава 12. Разработка отчетов 261 

    Третье, на что я хотел бы обратить ваше внимание в рассматриваемом 
примере, — использование информации о типах во время выполнения приложения 
(RTTI — RunTime Type Information). Доступ к такой информации в данном примере 
обеспечивается с помощью конструкции with-as. Хотя компилятор Delphi 
преобразует все операторы в выполняемый машинный код, вы, тем не менее, имеете 
возможность получить информацию о типе класса во время работы программы. 
Обычно компиляторы, генерирующие машинный код, не могут похвастать такой 
возможностью, так как это всегда было прерогативой таких интерпретирующих 
систем как Visual Basic. Системы, генерирующие машинный код, как правило не 
выполняют такой проверки, поскольку в самом машинном коде нет никакой 
информации о типе данных, которые он представляет. Когда исходный текст 
программы преобразуется в объектный код, то все переменные представляются в 
виде машинных адресов. Однако реализованный в Delphi механизм RTTI позволяет 
вам воспользоваться преимуществом сразу двух подходов — компиляцией в машинный 
код, с одной стороны, и получением информации о типах во время выполнения 
приложения, с другой. Благодаря наличию такой возможности программист может 
проверить тип компонентов формы и, в зависимости от результата, применить к 
ним те или иные процедуры. 
    Наконец, последнее, чем интересен приведенный выше программный фрагмент, — 
это использование приведения типа при установке свойств visible компонентов. 
Так как не все компоненты имеют данное свойство (например, такого свойства нет 
у невизуальных компонентов), поэтому следующие операторы могут привести к 
возникновению ошибки: 

    with Components[I] fo begin 
         Tag :=Longint(Visible) ; 
         Visible:=False; 
    end; 

    Поскольку в базовом классе компонентов нет свойства visible, то в данном 
фрагменте вообще не будет устанавливаться значение этого свойства для 
компонента Components [I]. Вместо этого при попытке выполнения оператора 
присвоения компилятор решит, что данное свойство относится к форме, так как 
обработчик события, в котором приведены эти операторы, принадлежит классу 
формы. Поэтому значение будет присвоено свойству visible формы, что не 
совпадает с вашими ожиданиями. 

    Средство Object Browser 

    Для того чтобы учесть последний нюанс, нужно хорошо знать, в каких классах 
имеется свойство visible. Одним из таких классов в иерархии визуальных 
компонентов Delphi является класс компонента TControl. Об этом я узнал, изучая 
иерархию классов Delphi. Для этого можно воспользоваться двумя способами. 
Первый, более простой, состоит в применении средства Object Browser. Если вы 
хотя бы один раз выполнили компиляцию проекта, в меню View Delphi становится 
доступным пункт Browser. Щелкните на команде View^ Browser, и на экране 
появится окно Object Browser. В левой панели этого окна приведена иерархия 
классов, в которой можно искать интересующие вас классы. Кроме того, можно 
выполнять поиск в иерархии по имени. Например, если вы найдете компонент 
TButton, то увидите, что он является непосредственным потомком компонента 
TButtonControl, который, в свою очередь, является непосредственным потомком 
компонента TWinControl. На правой панели можно увидеть, что среди прочих 
методов и свойств компонента TButton имеется и свойство Visible. 
    Второй способ исследования "генеалогического древа" определенного класса — 
изучение исходного текста библиотеки VCL. Из него можно почерпнуть немало 
полезной и ценной информации. Это не так просто, как использование Object 
Browser, однако из исходного текста можно узнать гораздо больше, чем из 
иерархии классов, представленной средством Object Browser. Начните с поиска 
модуля, в котором объявлен интересующий вас класс. Для облегчения поиска можно 
воспользоваться какой-нибудь утилитой (например, grep), однако поскольку 
разработчики компании Borland присваивают файлам модулей достаточно понятные 
имена, то найти нужный модуль не составит большого труда. Для того чтобы 
определить класс предка, нужно посмотреть на операторы объявления класса — эта 
информация содержится в первой строке объявления класса. Например, первая 
строка объявления класса TButton имеет следующий вид: 

    TButton = class(TButtonControl) 

    Это означает, что класс TButton является наследником класса 
TButtonControl. Если мы пойдем дальше, то обнаружим, что первая строка 
объявления класса TButtonControl имеет такой вид: 

    TButtonControl = class(TWinControl) 


    262 Часть II. Учебный курс 

    В данном случае можно сказать, что класс TButtonControl является 
наследником класса TWinControl. Компоненты TButton и TButtonControl объявлены 
в модуле Stdctrls. Исследовав объявления обоих классов, вы увидите, что ни в 
одном из них не встречается объявления свойства Visible, поэтому нужно перейти 
к изучению их общего предка — класса TWinControl. Этот класс объявлен в модуле 
Controls. Первая строка объявления класса TWinControl имеет следующий вид: 

    TWinControl = class(TControl) 

    Просмотрев объявление класса TWinControl, вы также не найдете в нем 
объявления свойства visible. И только изучив объявление класса TControl, вы 
наконец-то "доберетесь" до этого свойства. Именно поэтому для приведения типа 
компонентов при обращении к свойству visible мы использовали тип TControl. 

    На заметку 

    Приведение типа, пример которого содержится в рассматривавшемся фрагменте, 
называется приведением типа с проверкой (checked typecast). При использовании 
такого приведения типа во время выполнения программы происходит проверка типа 
объекта, для которого выполняется операция приведения. Если экземпляр класса 
имеет тип, отличный от типа, к которому приводится этот экземпляр, а также 
отличный от типов всех предков класса, используемого для приведения, то 
генерируется исключительная ситуация. 
    Имеется и другая форма приведения типа, при которой проверка типа не 
выполняется. Рассмотрим следующий пример.

    With TControl (Components [I] ) do 

    В данном случае выполняется приведение типа без проверки типа объекта. В 
случае приведения объекта к неправильному типу приложение, скорее всего, 
сгенерирует ошибку нарушения доступа и завершит работу. Приведение типа с 
проверкой обладает такой же функциональностью, как и приведение типа без 
проверки, но, в отличие от последнего, значительно надежнее и безопаснее. 
Поэтому всегда, где это возможно, используйте приведение типа с проверкой. 
    Кстати, в программном фрагменте, использовавшемся для печати формы, 
отсутствует проверка типа класса TBitBtn. Почему этого не делается? Ведь на 
форме могут быть компоненты TBitBtn, которые, естественно, не нужно печатать в 
отчете. По крайней мере, один такой компонент bbPrint, наверняка присутствует 
на рассматриваемой форме. Каким образом в приведенном ниже операторе проверки 
типов учитываются компоненты класса TBitBtn? 

    if (Components[I] is TButton) or 
    (Components[I] is TDBNavigator) or 
    (Components[I] is TSpeedButton) then 

    Ответ кроется в иерархии классов. Как вы уже знаете, оператор is 
возвращает значение True, когда определенный экземпляр класса принадлежит к 
указанному классу или к классу одного из предков указанного класса. Последняя 
часть этого определения и объясняет происходящее с компонентами TBitBtn. Так 
как класс TBitBtn является потомком класса TButton, то при проверке типа 
TBitBtn на соответствие типу TButton оператор is возвращает значение True. К 
сожалению, компоненты TSpeedButton не являются потомками класса TButton, как 
можно было предположить, поэтому для них нужно выполнять отдельную проверку. 

    А нельзя ли побыстрее? 

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

    DBNavigator.Visisble:=False; 
    bbOK.Visisble:=False; 
    bbCancel.Visisble:=False; 
    bbPrint.Visisble:=False; 

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


    Глава 12. Разработка отчетов 263 


изменений. Во-вторых, эта же проблема возникнет, если на любую форму, 
являющуюся предком данной формы, будет добавлен какой-нибудь не подлежащий 
печати компонент (например, кнопка). В-третьих, в данном обработчике не 
допускается предположение, что какой-то из перечисленных элементов управления 
может быть невидимым изначально. Если понадобится учесть фактор видимости, то 
в исходный текст последнего обработчика события придется добавить операторы, 
сохраняющие текущее значение свойства Visible каждого скрываемого компонента. 
Кроме того, после печати формы понадобится восстановить исходные значения этих 
свойств. В конце концов в исходном тексте второго обработчика будет 
практически столько же, если не больше, строк, но при этом подход будет менее 
гибким. Наконец, максимизированная форма удобна с точки зрения печати отчета, 
но не с точки зрения работы с приложением. По тем или иным соображениям может 
понадобиться, чтобы форма занимала только часть экрана. В таком случае вы не 
должны максимизировать ее во время разработки. 
    Рассмотренный пример обработчика события Ondick кнопки bbprint можно 
использовать в любых приложениях, созданных с помощью Delphi, для печати любой 
формы. Можно даже поместить эту кнопку и обработчик события щелчка на ней в 
класс fmAnyForm иерархии форм приложения, чтобы распространить возможность 
печати сразу на все формы системы RENTMAN. 

    Печать нескольких строк 

    Без особого труда можно распечатать не только текущую строку таблицы, но и 
все остальные строки. Например, для того чтобы распечатать все наряды, 
хранимые в базе данных, можно использовать следующие операторы: 

    With taWORDER do begin 
        First; 
        While not(EOF) do begin 
            fmRWORMDEO.bbPrintCiick(Self) ; 
            Next; 
        end; 
    end; 


    Специальные формы для печати 

    Нет ничего плохого в том, чтобы создать специальные формы, предназначенные 
только для печати. Например, можно унаследовать класс формы нарядов вида 
"главная-подчиненная" и на его основе создать форму, предназначенную только 
для печати нарядов. Форму, предназначенную для печати, можно настроить таким 
образом, чтобы как можно лучше выполнялись требования, предъявляемые к 
отчетам. Например, можно изменить используемую по умолчанию цветовую гамму 
(например, отказаться от широко распространенной комбинации черного и серого 
цветов) и подобрать цвета, облегчающие восприятие отчета. Кроме того, на форме 
печати можно использовать дополнительные границы и линии, что также 
способствует улучшению вида отчета. Если форма предназначена только для 
печати, то свойства visible тех компонентов, которые не должны отображаться в 
отчете, можно установить в False еще на этапе разработки формы. В данной главе 
мы не будем рассматривать такие формы, однако это не означает, что их нельзя 
использовать в разрабатываемых вами приложениях. 

    На заметку 

    Создание отчетов с помощью печати самих форм — строго говоря, не самый 
эффективный путь получения выходной информации. Дело в том, что при печати 
форм задание на печать формируется как задание печати большого растрового 
изображения, что требует наличия у принтера значительного объема памяти. 
Некоторые принтеры могут вообще не поддерживать печать графики. Кроме того, 
печать форм требует больших временных затрат, чем печать эквивалентных 
отчетов, созданных с помощью других средств. Наконец, поскольку при печати 
формы фактически происходит распечатка копии экрана, то качество получаемой 
распечатки зависит не столько от разрешающей способности принтера, сколько от 
разрешающей способности видеоадаптера. 

    Альтернативные методы создания форм-отчетов 

    Формы-отчеты можно также создать как с помощью компонентов QuickReport 
Delphi, так и с помощью коммерческих конструкторов отчетов. При разработке 
сложной формы-отчета один из только что указанных способов может оказаться 
более простым и быстрым, чем создание специальной формы, пред


    264 Часть II. Учебный курс 

назначенной для печати отчета. Для простых форм-отчетов, которые часто 
используются во внутреннем делопроизводстве, вполне достаточно метода print 
класса трогт. Однако будет нелишним подчеркнуть, что компоненты семейства 
QuickReport позволяют без особых проблем справляться как с простыми, так и со 
сложными отчетами. 

    Тестирование формы-отчета 

    Итак, все готово для тестирования нового отчета. Сохраните проект и 
запустите приложение на выполнение. Выберите пункт меню Reports^Work Order или 
нажмите <F7>. На экране должна появиться форма Work Order Selection. Выберите 
наряд, а затем щелкните на кнопке Edit. В открывшемся окне щелкните на кнопке 
Print. Окно должно автоматически максимизироваться, из него должны исчезнуть 
все кнопки, после чего будет сформировано задание на печать, и восстановится 
прежний вид окна. Выйдите из приложения и вернитесь в Delphi. 

    На заметку 

    Для управления масштабированием формы при печати с помощью метода Print 
применяйте свойство printScale класса TForm. Используемое по умолчанию 
значение poProportional этого свойства приводит к тому, что при печати формы 
сохраняются пропорции ее изображения на экране. Для этого при печати 
выбирается такое же количество точек на дюйм, как и при отображении формы. 
Значение poPrintToFit приводит к тому, что форма печатается с соблюдением 
пропорций, но размер ее изображения изменяется в соответствии с размером 
страницы. При выборе значения poNone масштабирование изображения формы 
отключается. Это значение можно использовать, когда вид формы на экране сильно 
отличается от отпечатанного изображения из-за большого несовпадения 
разрешающей способности принтера и дисплея. 
    Следующий отчет будет создан с помощью компонентов семейства QuickReport. 
Проделав описанные в следующем разделе практические задания, вы увидите, что 
разработка отчетов с помощью компонентов QuickReport лишь немного сложнее 
создания обычных форм с использованием стандартных визуальных компонентов. 
Кроме того, вы убедитесь, что компоненты QuickReport позволяют справиться с 
задачей разработки отчета практически любой сложности, причем без особых 
затрат усилий и времени. 

    Табличный отчет PROPERTY Listing Report 

    Отчет, который мы создадим в данном разделе, представляет собой данные 
таблицы PROPERTY. Для того чтобы ускорить освоение компонентов QuickReport, мы 
воспользуемся мастером отчетов QuickReport Wizard Delphi. 
    Щелкните на пункте меню File'=>New и в диалоговом окне New Items выберите 
вкладку Business. Щелкните дважды на элементе QuickReport Wizard, чтобы 
запустить мастер отчетов. Работа с мастером заключается в предоставлении 
ответов на задаваемые им вопросы. По окончании диалога мастер создает отчет на 
основе предоставленной ему информации. 
    В первом окне мастера вам будет предложено выбрать псевдоним каталога или 
базы данных. Выберите из предоставленного списка псевдоним dbRENTMAN. 
    В нижней части диалогового окна мастера выберите таблицу, по которой будет 
создаваться отчет. Как уже говорилось выше, в данном случае нужно выбрать 
таблицу PROPERTY. Щелкните на кнопке Next. 
    В следующем диалоговом окне мастер отчетов попросит вас сообщить, какие 
поля таблицы нужно отобразить в отчете. Выберите поля PROPERTY_NUMBER, 
ADRRESS, CITY, STATE, ZIP, ADDITION и SCHOOLDISTRICT. Для перемещения полей 
между списками Available и Selected используется двойной щелчок или кнопки 
перемещения выбранного поля. Выбрав все указанные выше поля, щелкните на 
кнопке Next. 
    В последнем диалоговом окне мастер отчетов попросит вас указать заголовок 
отчета. Введите название PROPERTY Listing Report. В этом же окне, при желании, 
можно выбрать другой шрифт отчета, однако я думаю, что используемый по 
умолчанию шрифт Arial в данном случае вполне хорош. 
    Определив заголовок отчета, щелкните на кнопке Finish, после чего мастер 
отчетов сгенерирует предварительный отчет, который вы затем можете изменять по 
своему усмотрению. 
    Первое, что можно сказать о полученном отчете, кроме того, что он прост и 
бесхитростен, — это то, что отчет является "живым", работающим объектом. 
Установите свойство Active табличного объекта taPROPERTY равным True, а затем 
щелкнув правой кнопкой мыши на отчете, выберите из появившегося меню команду 
Preview, чтобы увидеть, как отчет будет выглядеть во время работы программы 
(рис. 12.1). 


    Глава 12. Разработка отчетов 265 


    Рис. 12.1. Предварительная версия отчета, созданная мастером QuickReport Wizard 

    Puc. 12.2. Тот же отчет после небольшой доработки 


    Часть II. Учебный курс 266

    
    Я решил не вдаваться в подробности настройки всех компонентов QuickReport, 
задействованных в отчете, а лишь представить вам результат (рис. 12.2), 
который был получен на основании того же отчета, созданного мастером отчетов. 
    Кратко остановлюсь на тех изменениях, которые я внес в исходную версию отчета. 
    Q Итоговые значения. Обратите внимание на счетчик количества объектов 
недвижимости, приведенный в нижней части отчета. Компоненты QuickReport 
поддерживают создание групп, вычисление промежуточных итогов и выполнение 
промежуточной сортировки и т.д. 
    Q Цвета. Для того чтобы выделить определенные фрагменты отчета, я применил 
разные цвета. 
    Q Системные поля. Обратите внимание на дату и время в верхнем левом углу 
отчета. Для их создания использовался компонент QRSysData семейства 
компонентов QuickReport. 

    Совет 

    Не удивляйтесь, когда столкнетесь с тем, что компонент QRExpr, 
предназначенный для создания выражений, не поддерживает стиля ANSI SQL при 
записи вычисляемых столбцов. Например, не используйте запись COUNT (*) для 
получения общего количества строк в таблице. Не будет также работать выражение 
COUNT (PRO PERTY_NUMBER) . Ключевое слово-COUNT при использовании его в 
выражениях, создаваемых с помощью компонента QRExpr, нужно записывать само по 
себе и без скобок, что, без сомнения, не соответствует синтаксису ANSI SQL. 

    На заметку 

    Исходный текст всех отчетов, приведенных в данной главе, находится на 
прилагаемом к книге компакт-диске. 

    Закончив настройку отчета, сгенерированного мастером QuickReport Wizard, 
назовите соответствующий объект frRPROLSTO и сохраните программный модуль 
отчета в файле RPROLSTO. РАЗ. 

    Подключение отчета к приложению 

    Для того чтобы всесторонне протестировать отчет, нужно подключить его к 
приложению RENTMAN. Для этого нужно выполнить следующие действия. 
    Q Подключить программный модуль формы отчета к программному модулю 
приложения RSYSMANO. 
    Q Добавить элемент меню и соответствующие операторы в исходный текст 
программного модуля главной формы приложения, чтобы связать новый отчет с меню 
системы RENTMAN. 
    Сначала откройте главную форму приложения fmRSYSMANO в окне конструктора 
форм. Выберите команду File^Use Unit и дважды щелкните на элементе RPROLSTO 
открывшегося списка. Теперь из модуля главной формы вы сможете обращаться к 
модулю формы отчета. 
    Щелкните дважды на компоненте MainMenu главной формы, а затем щелкните на 
пустом элементе меню, находящемся в подменю Reports. Установите заголовок 
(свойство Caption) элемента меню равным sProperty List, а свойству Shortcut 
присвойте значение F11. Щелкните дважды на новом элементе меню и в исходный 
текст обработчика события введите следующий оператор: 

    frRPROLSTO.QuickRepI.Preview; 

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


    Глава 12. Разработка отчетов 267 


    Рис. 12.3. Отчет PROPERTY Listing Report во время работы приложения 

    Отчет в виде списка заданий 

    Последний отчет, который мы создадим в этой главе, построен только на 
использовании конструкций языка Object Pascal. Данный подход покажет вам, 
насколько сложной была задача, стоявшая перед разработчиками компонентов 
QuickReport Delphi. Использование в практических целях Object Pascal вместо 
визуальных компонентов может быть оправдано лишь в особых случаях и является 
скорее исключением, чем правилом. Создание отчетов таким способом имеет смысл 
только в том случае, когда вам нужен полный доступ ко всем объектам приложения 
и ресурсам системы. Поскольку язык Object Pascal изначально разрабатывался как 
язык программирования общего назначения, с его помощью можно создавать 
программные системы, которые ни в чем не уступают аналогичным системам, 
созданным с помощью таких языков как С, BASIC, Pascal и т.д. Возможности языка 
Object Pascal настолько широки, что вряд ли можно придумать такой отчет, 
который нельзя было бы реализовать с помощью этого языка. 
    Поместите на форму модуля данных dmRENTMAN новый компонент Query. Назовите 
объект запроса quTaskList, а в качестве значения его свойства SQL введите 
следующий запрос. 

    SELECT E.Name, P.Address, P.City, P.Addition, Т.Description, T.TaskDuration 
    FROM EMPLOYEE E, 
    WORDER W, 
    PROPERTY P, 
    WODETAIL D, 
    WORKTYPE T 
    WHERE E.EMPLOYEE_NUMBER=W.EMPLOYEE_NUMBER 
    and W.WORDER_NUMBER=D.WORDER_NUMBER 
    and W.PROPERTY_NUMBER=P.PROPERTY_NUMBER 
    and D.WORK_TYPE_CODE=T.WORK_TYPE_CODE 
    and W.StartDate <= :ListDate 
    and W.EndDate >= :ListDate 
    ORDER BY E.Name, P.Address, P.City, P.Addition, T.Description 

    В этом запросе создается объединение таблиц EMPLOYEE, WORDER, PROPERTY, 
WODETAIL и WORKTYPE для получения полного перечня заданий, выполняющихся 
каждым рабочим в определенный день. Обратите внимание на наличие у запроса 
одного параметра :ListDate, обязательного для нормальной работы этого 


    268 Часть II. Учебный курс 

 запроса. Откройте окно редактирования свойств params компонента Query и 
установите тип параметра ListDate равным Date. Измените значение свойства 
Database на dbRENTMAN, а затем дважды щелкните на компоненте и добавьте все 
его поля как компоненты TField. 
    Теперь осталось создать исходный текст процедур, генерирующих отчет, на 
языке Object Pascal. Откройте в окне конструктора форм главную форму 
приложения, fmRSYSMANO, и дважды щелкните на ее компоненте MainMenu, чтобы 
открыть окно конструктора меню. Щелкните на пункте Task List меню Reports и 
укажите F10 в качестве значения свойства Shortcut объекта элемента меню. Затем 
щелкните дважды на пункте Task List и в открывшемся окне введите исходный 
текст обработчика события OnClick, приведенный в листинге 12.2. 

    Листинг 12.2. Обработчик события TaskListlClick 

    procedure TfmRSYSMANO.TaskListlClick(Sender: TObject); 
    var 
       LastName : String; 
       CurrentLine : Byte; 

       procedure PrintColumnHeadings; 
       begin 
        Writeln(PrintFile,'     '+Pad('Address',30) , 
            Pad('City',20),Pad('Addition',20), 
           Pad('Work',30),Pad('Time (Days)',15)); 
       end; 

    begin 
        inherited; 
        try 
        Cursor:=crHourGlass; 
             With dmRENTMAN, quTaskList do begin 
             ParamByName('ListDate').AsDate:= 
               StrToDatef'04/05/97') ; 
                //    Можете указать другую дату, в зависимости от 
                //    используемых для тестирования данных. 
               //    ParamByName('ListDate').AsDate:=Date; 
           Open; 
           try 
           BeginReport(poPortrait,Caption) ; 
          try 
          While not eof do begin 
             CurrentLine: =9; 
             PrintHeader('RTSKLSTO',Caption,'Employee Task List', 
             ParamByName('ListDate').AsString) ; 
             PrintColumnHeadings; 
             While (not eof) and (CurrentLineOPageLength) do begin 
                  Writeln(PrintFile); 
                 Writeln(PrintFile,'EMPLOYEE:  '+quTaskListName.AsString); 
                 Inc(CurrentLine,2) ; 
                Repeat 
                    LastName:=quTaskListName.AsString; 
                   Writein(PrintFile,'     '+Pad(quTaskListAddress.AsString,30), 
                       Pad(quTaskListCity.AsString,20), 
                       Pad(quTaskListAddition.AsString,20), 
                      Pad(quTaskListDescription.As String,30), 
                      Pad(quTaskListTaskDuration.AsString,15) ); 
                 Next; 
                 Inc(CurrentLine) ; 
            Until (eof) or (CurrentLine=PageLength) or 
                 (quTaskListName .AsStringOLastName) ; 
         end; 
    end; 
    finally 
         EndReport; 
    end; 
    finally 


    Глава 12. Разработка отчетов 269 

                 quTaskList.Close; 
            end; 
       end; 
       finally 
       Cursor:=crDefault; 
       end; 
    end. 

    В данном обработчике используются несколько вспомогательных функций и 
процедур. Добавьте приведенные в листинге 12.3 подпрограммы в модуль RSYSMANO, 
разместив их перед методом TaskListlclick. 

    Листинг 12.3. Вспомогательные функции и процедуры для создания 
отчета-списка заданий 

    function Pad(InStr : String; TotalLen : Integer) : String; 
    begin 
         Result:=InStr; 
         While (Length(Result)<TotalLen) do Result:=Result+' '; 
    end; 

    function LPad(InStr : String; TotalLen : Integer) : String; 
    begin 
           Result:=InStr; 
           While (Length(Result)<TotalLen) do Result:^' '+Result; 
    end; 

    function Center(InStr : String; TotalLen : Integer) : String; 
    var 
        NumSpace : Integer; 
        Temp : String; 
    begin 
        NumSpace := (TotalLen-Length(InStr)) div 2; 
        Temp:='' ; 
        While Length(Temp)<NumSpace do Temp:=Temp+' '; 
        Result:=Temp+InStr+Temp; 
        While Length(Result)<TotalLen do Result:=Result+' '; 
    end; 

    procedure PrintHeader(ReportName,SystemTitle,ReportTitle,Criteria : String); 
    begin 
          If (Printer.PageNumber <> 1) then Write(PrintFile,^L) ; 
          Writein(Print File,Pad('Report: '+ReportName,25), 
              Center(SystemTitle,LineLength-45), 
              LPad('Page: '+IntToStr(Printer.PageNumber),25)); 
         Writein(PrintFile) ; 
         Writein(PrintFile,Pad('Print date: '+DateToStr (Date),25), 
            Center(ReportTitle,LineLength-45), 
            LPad('Print time: '+TimeToStr(Time),25)) ; 
        Writein(PrintFile) ; 
        Writein (PrintFile, Pad ( 'User name: ' 4-UserName, 25) , 
              Center('For: 4-Criteria,LineLength-45)) ; 
        Writein(PrintFile) ; 
    end;

    procedure BeginReport(Orientation : TPrinterOrientation; Title :String); 
    begin 
         Printer.Orientation:^Orientation; 
         Printer.Title:=Title; 
        AssignPrn(PrintFile) ; 
        Rewrite(PrintFile) ; 
        With Printer.Canvas.Font do begin 
             Name:='Courier New'; 
             Height:=-13 ; 
             Style:=[]; 
       end; 
       LineLength:=(Printer.PageWidth div Printer.Canvas.TextWidth('X'))-5; 
    

    270 Часть II. Учебный курс 

       PageLength:=(Printer.PageHeight div Printer.Canvas.TextHeight('X'))-2; 
    end; 
  
    procedure EndReport; 
    begin 
       CloseFile(PrintFiie); 
    end; 
   
    Приведенные в листингах 12.2 и 12.3 процедуры и функции используют 
несколько глобальных переменных модуля RSYSMANO. Найдите в исходном тексте 
модуля строку {$R *.DFM} и разместите под ней следующие объявления глобальных 
идентификаторов модуля RSYSMANO. 

    var 
       PrintFiie : Text; 
       LineLength : Integer; 
       PageLength : Integer; 
       UserName : String; 

    Кроме того, в только что созданном исходном тексте используется модуль 
Printers, который обеспечивает интерфейс со стандартными функциями печати 
Windows. Поместите вызов модуля Printers в оператор uses модуля RSYSMANO. 

    Обработчик события TaskListlClick 

    В исходном тексте процедуры обработчика события TaskListlClick имеется 
несколько заслуживающих внимания аспектов. Во-первых, посмотрите, как 
используется стандартная процедура Writein языка Object Pascal для вывода на 
печать. Для облегчения работы с этой процедурой использована процедура 
AssignPrn. Последняя связывает текстовый файл вывода с используемым по 
умолчанию устройством печати Windows. Таким образом, после применения 
процедуры AssignPrn можно открыть соответствующий текстовый файл и выводить в 
него информацию, как на стандартное устройство вывода (по умолчанию — экран). 
Данные при этом будут пересылаться на устройство печати. 
    Существуют различные мнения относительно того, какой подход к печати 
текста из приложений Windows считать правильным. Согласно одному из них, для 
начала печати задания нужно использовать процедуру BeginDoc, а для вывода 
каждой очередной строки — процедуру TextOut. Этот метод обладает рядом 
преимуществ, включая возможность полного управления видом текста и обеспечения 
точности его расположения. Однако имеются и недостатки, главный из которых — 
сложность настройки и использования такого способа печати. Если вам нужна 
возможность полного контроля над выводимым текстом, советую воспользоваться 
полнофункциональным конструктором отчетов — в таких средствах есть все 
необходимое для управления печатью. 
    Многие разработчики придерживаются другого подхода, продемонстрированного 
приведенными выше листингами. Основная концепция, лежащая в основе этого 
подхода, заключается в максимальном упрощении выводимой информации — все 
отчеты, которые требуют более или менее значительных усилий, нужно 
разрабатывать с помощью конструкторов отчетов. Используя стандартные процедуры 
вывода, вы ограничиваетесь только теми возможностями, которые они 
предоставляют, не занимаясь такими вопросами, как украшение отчетов. Этот 
подход, направленный на упрощение, позволяет создавать компактные и понятные 
процедуры и функции вывода отчетов. 

    BeginReport 

    Давайте внимательнее рассмотрим все составляющие приведенных выше 
программных фрагментов. Начнем с процедуры BeginReport. 

    procedure BeginReport(Orientation : TPrinterOrientation; Title :String); 
    begin 
       Printer.Orientation:=0rientation; 
       Printer.Title:=Title; 
       AssignPrn(PrintFiie) ; 
       Rewrite(PrintFiie) ; 
       With Printer.Canvas.Font do begin 
          Name:='Courier New'; 
          Heights-13; 
         Style:=[]; 
       end; 
       LineLength:=(Printer.PageWidth div Printer.Canvas.TextWidth('X'))-5; 
       PageLength:=(Printer.PageHeight div Printer.Canvas.TextHeight('X'))-2; 
    end; 


    Глава 12. Разработка отчетов 271 

    Как видно из ее исходного текста, значение, определяющее ориентацию 
будущего отчета, передается в процедуру в виде переменной типа 
TPrinterOrientation. Данный тип объявлен в модуле printers и имеет всего два 
значения — poPortrait и poLandscape. Поскольку определение ориентации обычно 
выполняется в вызывающей процедуре, то для правильной инициализации задания 
печати процедуре BeginReport нужно передать соответствующее значение. 
    В операторном блоке with printer.Canvas.Font устанавливаются параметры 
шрифта, которым будет печататься отчет. Для упрощения в данном случае весь 
отчет выводится с использованием одного шрифта. Более того, этот шрифт 
является моноширинным TrueType-шрифтом Courier New. Можете попробовать решить 
задачу использования пропорциональных шрифтов в отчетах, если вам некуда 
девать время или жизнь кажется скучной и неинтересной (лично я от этого воздержусь). 
    Обратите также внимание на то, как вычисляются значения длины строки 
(LineLength) и длины страницы (PageLength): 

    LineLength:=(Printer.PageWidth div Printer.Canvas.TextWidth('X'))-5; 

    Текущий размер страницы в пикселях (с учетом ориентации, конечно) делится 
на ширину символа 'х' текущего шрифта. Вот еще одна веская причина для 
использования моноширинных шрифтов. Поскольку в таких шрифтах все символы 
имеют одинаковую ширину, то с помощью приведенных выше несложных вычислений, в 
которых используется метод TextWidth, можно получить длину строки. При 
использовании пропорционального шрифта от результата таких простых вычислений 
будет мало проку — потребуется гораздо более сложный алгоритм, учитывающий 
разную ширину символов. Заметьте также, что от результатов вычислений 
отнимается значение 5, чтобы обеспечить наличие правой и левой границ. 
    Параметр Title, передаваемый в процедуру, используется для присвоения 
имени заданию печати, которое необходимо для системы буферизации печати 
Windows. Это имя отображается также на разделительных страницах при печати на 
сетевых принтерах. Если ваш сетевой принтер поддерживает режим печати таких 
страниц, можете использовать свойство Title компонента TPrinter для создания 
текста, выводимого на разделительных страницах. 
    Функции Pad, LPad и Center по своему назначению подобны. Одной из типичных 
проблем при форматировании вывода является выравнивание столбцов, содержащих 
данные переменной длины. Другая из таких задач — установление ширины столбцов, 
соответствующей размеру данных, выводимых в этих столбцах. Если заголовок 
столбца значительно превышает максимальную длину данных, значит ширину 
заголовка нужно уменьшить, если же, наоборот, ширина данных превышает ширину 
заголовка — заголовок нужно увеличить путем добавления к нему пробелов. Для 
решения этих задач и предназначены данные функции, которые преобразуют 
элементы отчета таким образом, чтобы они имели одинаковую ширину. При 
использовании моноширинных шрифтов добавление пробелов для получения 
одинаковой ширины является одним из главных методов получения отчетов, имеющих 
привлекательный внешний вид. 

    PrintHeader 

    Я постарался так написать процедуру PrintHeader, чтобы без особых 
переделок ее можно было использовать в любых других программах. Хотя я и не 
рекомендовал вам создавать отчеты с помощью одних лишь операторов программы, 
если возникнет ситуация, когда без такого подхода не обойтись, можете 
воспользоваться примерами из данной главы, в том числе и процедурой 
PrintHeader. В качестве параметров этой процедуре передаются строки, 
представляющие имя отчета (ReportName), системный заголовок (SystemTitle), 
заголовок отчета (ReportTitle), а также критерий (Criteria). Это обеспечивает 
данной процедуре гибкость, благодаря которой вы можете применять ее для печати 
любых отчетов. 

    С помощью оператора 

    If (Printer. PageNumber о 1) then Write (PrintFile, •"L) ; 

на принтер при каждом вызове процедуры PrintHeader, за исключением первого 
раза, отправляется управляющий символ с кодом 12. Получение этого кода 
практически любым принтером приводит к переходу на новую страницу. Чтобы зря 
не расходовать бумагу, переход на новую страницу не выполняется при печати 
первой и последней страниц. 
    Обратите внимание на использование встроенных функций Date и Time. Если 
время и дата печати разрабатываемого отчета очень важны, то лучше сохранить 
значения, возвращаемые этими функциями, во вспомогательных переменных, которые 
затем будут печататься на каждой странице отчета, а не обращаться каждый раз к 
самим этим функциям. Дело в том, что при каждом обращении к функции Time она 
возвращает новое время, соответствующие показанию системных часов. Это 
приведет к тому, что на всех страницах отчета будет стоять разное время. Это 
касается и функции Date, если печать отчета начинается до полуночи, а 
заканчивается после полуночи. В данной процедуре я использую непосредственный 
вызов этих функций только в целях упрощения исходного текста. 


    272 Часть II. Учебный курс 

    Наконец, хочу обратить ваше внимание на то, как с помощью функции Center и 
переменной LineLength обеспечивается вывод системного заголовка, заголовка 
отчета и критерия отчета посередине страницы. Такой подход позволяет изменить 
ориентацию страницы без необходимости переделки операторов процедуры печати 
заголовка. Если ориентация изменится с альбомной на книжную или наоборот, 
расположение заголовка также изменится в соответствии с ней. 

    Еще немного о TaskListlClick 

    С помощью оператора 

    ParamByName('ListDate').AsDate:=Date; 

текущая системная дата передается запросу quTaskList в качестве параметра 
ListDate. Если вы хотите, чтобы отчет создавался по текущей дате, уберите 
символы комментария, стоящие перед этим оператором, и поставьте их перед 
предыдущим оператором, в котором используется дата 5 апреля 1997 года. После 
присвоения значения параметру ListDate вызывается метод Open объекта 
quTaskList, выполняется запрос, в результате работы которого возвращается 
полученный набор данных. 
    Обратите внимание на использование блоков try-finally для обработки 
возможных исключительных ситуаций — в данном обработчике события используются 
три таких блока. Один блок обеспечивает корректное закрытие запроса при выходе 
из обработчика. Второй блок гарантирует закрытие текстового файла, который 
использовался для вывода на устройство печати Windows, независимо от того, 
возникли или нет какиелибо ошибки при выполнении операторов обработчика 
события. Наконец, последний блок предназначен для возвращения вида указателя 
мыши в обычное состояние по завершении процесса печати. 
    Цикл repeat-until (quTeskListName .AsStringobastName) управляет созданием 
групп в отчете. Главная цель, для которой создавался этот отчет — представить 
список заданий для каждого рабочего. Эта цель достигается путем использования 
ключевого слова ORDER BY в операторе SQL запроса, а также с 
помощыо""алгоритма, реализуемого вышеупомянутым циклом. 
    Наконец, последнее, о чем я хотел бы упомянуть. Обратите внимание на то, 
как в начале выполнения операторов обработчика события изменяется, а в конце — 
восстанавливается форма указателя мыши. Всегда нужно помнить о необходимости 
извещения пользователя о причине задержки в работе программы. В данном случае 
с этой целью меняется форма указателя — с обычной стрелки на песочные часы, 
как это принято в Windows для обозначения состояния ожидания готовности 
системы. Чем активнее обратная связь с пользователем, тем лучше. Еще одним 
способом решения подобных задач является использование компонента 
TProgressBar, который визуально отображает состояние подготовки отчета. В этом 
случае по мере генерирования строк набора данных, возвращаемого компонентом 
quTaskList, можно в соответствующей степени изменять положение бегунка панели. 
Такие небольшие украшения придают приложению законченный вид, а также слегка 
развлекают пользователя, которому не приходится гадать, сколько времени 
осталось до окончания процесса. 

    Предварительный просмотр отчета 

    Еще одна возможность, которой нужно снабдить приложение, — средство 
предварительного просмотра отчета перед его печатью. Для этого можно, 
например, воспользоваться процедурой Assign, а не процедурой AssignPrn. В 
результате вы получите обычный текстовый файл, который затем можно 
откорректировать, распечатать или удалить. Для того чтобы просмотреть этот 
файл в приложении, созданном с помощью Delphi, воспользуйтесь свойством 
LoadFromFile компонента TMemo, чтобы представить содержимое файла в окне 
прокрутки. Для того чтобы распечатать находящийся в окне предварительного 
просмотра отчет, отправьте его на принтер, использовав сначала процедуру 
AssignPrn для инициализации задания печати, а затем свойство Lines компонента 
TMemo, которое представляет собой массив строк отчета. 
    Сохраните проект, нажмите <F9> для того, чтобы скомпилировать и запустить 
приложение. Когда система RENTMAN загрузиться, нажмите <F10>, чтобы 
распечатать список заданий. 

    Резюме 

    Вы ознакомились практически со всеми методами разработки отчетов в Delphi. 
Мы рассмотрели, как создать отчет путем печати самой формы, с помощью 
компонентов QuickReport а также путем использования программного кода на языке 
Object Pascal. В общем случае лучшим методом создания отчетов является 
применение компонентов семейства QuickReport. В любом случае, вы теперь готовы 
к разработке отчетов любой сложности в приложениях баз данных, создаваемых с 
помощью Delphi. 


    Глава 12. Разработка отчетов 273 

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

    В главе 13, "Последние штрихи", мы слегка приукрасим приложение RENTMAN и 
придадим ему некоторые дополнительные свойства. Эта глава посвящена вопросам 
улучшения внешнего вида и работы приложения. Из нее вы узнаете, как создать 
фоновое изображение на главной форме, пиктограмму приложения, справочную 
систему, строку состояния и др. В результате мы получим не только 
полнофункциональное, но и имеющее внушительный вид приложение. 


    274 Часть II. Учебный курс