Агрегатные поля
Агрегатные поля не входят в структуру полей набора данных, т. к. агрегатные функции подразумевают объединение записей таблицы для получения результата. Следовательно, значение агрегатного поля нельзя связать с какой-то одной записью, оно относится ко всем или группе записей.
Агрегатные поля не отображаются вместе со всеми полями в компонентах TDBGrid, в Редакторе полей они расположены в отдельном списке. Для представления значения агрегатного поля можно воспользоваться одним из компонентов отображения данных, который визуализирует значение одного поля (например, TDBText или TDBEdit) или свойствами самого поля:
LabelI.Caption := MyDataSetAGGRFIELDl.AsString;
Подробно вопросы создания агрегатных полей рассмотрены в гл. 13.
Класс TAggregateField предназначен для инкапсуляции свойств и методов агрегатных полей.
Его свойство
property Expression: string;
задает вычисляемое выражение.
Вычисление значения проводится только для тех агрегатных полей, свойство
property Active: Boolean;
которых имеет значение True.
Вычисление включенных свойством Active агрегатных полей выполняется только в том случае, если булевское свойство AggregatesActive клиентского компонента набора данных имеет значение True.
По умолчанию экземпляр класса TAggregateField создается со свойством Visible = False.
Агрегаты
Наличие локального буфера данных позволяет компоненту TClientDataSet реализовать ряд дополнительных функций, основанных на использовании агрегатных функций применительно к полям всего набора данных, загруженного в локальный буфер.
К агрегатным функциям относятся:
AVG — вычисляет среднее значение; COUNT — возвращает число записей; MIN — вычисляет минимальное значение; МАХ — вычисляет максимальное значение; SUM — вычисляет сумму.Для их применения в компоненте TClientDataSet предусмотрены:
индексированный список объектов, инкапсулирующих агрегатные выражения — агрегаты; агрегатные поля, обеспечивающие получение новых значений подобно вычисляемым полям, но с группированием записей на основе использования агрегатных функций.Дополнительные свойства полей клиентского набора данных
Как известно, все классы полей имеют одного общего предка — класс TField. Подробно эти классы рассматриваются в гл. 13. Здесь же остановимся лишь на нескольких дополнительных свойствах полей, которые работают только в режиме кэширования в обычных компонентах, инкапсулирующих набор данных, и в компоненте TClientDataSet. Причем в компоненте TClientDataSet реализация этих свойств обеспечена локальным кэшем.
Итак, для разработчика могут быть полезны свойства объектов полей, содержащие не только текущее, но и предыдущее значение поля.
Свойство
property CurValue: Variant;
возвращает текущее значение поля.
Свойство
property OldValue: Variant;
содержит значение поле, которое было до начала редактирования. Свойство
property NewValue: Variant;
содержит новое значение, которое может быть присвоено при обработке ошибки сервера методом-обработчиком onReconclieError (см. ниже).
Группировка и использование индексов
Каждый агрегат (объект или поле) имеет свойство
property GroupingLevel: Integer;
которое задает уровень группировки полей набора данных при вычислении. При значении 0 расчет проводится для всех записей набора данных. При значении 1 записи группируются по первому полю набора данных и расчет осуществляется для каждой группы. При значении 2 записи разбиваются на группы по первому и второму полям и т. д.
Однако группировка по уровням выше нулевого возможна, только если в наборе данных используется индекс по группирующим полям. Например, если свойство GroupingLevel = 2 и набор данных начинается с полей CustNo и OrderNo, в свойстве IndexName компонента TClientDataSet и свойств property IndexName: String; агрегата (объекта или поля) должно быть имя индекса, включающего оба эти поля.
Использование индексов
Обычно использование индексов — прерогатива сервера БД. Из компонентов Delphi только табличные компоненты могут в какой-то степени управлять использованием индексов. Очевидно, что удаленное соединение не способствует эффективному управлению индексами набора данных на сервере. Поэтому компонент TclientDataSet предоставляет разработчику возможность создавать и использовать локальные индексы.
Правильно созданные и используемые локальные индексы могут существенно ускорить выполнение операций с набором данных. В то же время их невозможно сохранить вместе с набором данных локально, их необходимо перестраивать при каждом новом открытии набора данных и его обновлении с сервера.
Для создания локального индекса используется метод
procedure Addlndex(const Name, Fields: string;
Options: TIndexOptions;
const DescFields: string = '';
const CaselnsFields: string = '';
const GroupingLevel: Integer = 0);
Параметр Name определяет имя нового индекса. Параметр Fields должен содержать имена полей, которые разработчик хочет включить в индекс. Имена полей должны разделяться точкой с запятой. Параметр options позволяет задать тип индекса:
TIndexOption = (ixPrimary, ixUnique, ixDescending, ixCaselnsensitive, ixExpression, ixNonMaintained);
TIndexOptions = set of TIndexOption;
ixPrimary — первичный индекс;
ixUnique — значения индекса уникальны;
ixDescending — индекс сортирует записи в обратном порядке;
ixCaselnsensitive — индекс сортирует записи без учета регистра символов;
ixExpression — в индексе используется выражение (для индексов dBASE);
ixNonMaintained — индекс не обновляется при открытии таблицы.
При этом можно задать поля, порядок сортировки которых будет обратным. Для этого их необходимо перечислить через точку с запятой в параметре DescFields. Параметр CaselnsFields аналогичным образом позволяет задать поля, на сортировку которых не влияет регистр символов.
Параметры DescFields и CaselnsFields используются вместо параметра Options.
Параметр GroupingLevel задает уровень группировки полей индекса. Подробнее об этом см. ниже в разд. "Агрегаты" этой главы.
Основные свойства компонента, обеспечивающие управление индексами, совпадают с аналогичными свойствами табличных компонентов (подробнее об этом см. гл. 12). Поэтому лишь кратко перечислим их.
При работе с компонентом разработчик имеет возможность управлять индексами.
Созданный индекс подключается к набору данных свойством
property IndexName: String;
которое должно включать имя индекса или использовать свойство
property IndexFieldNames: String;
в котором можно задать произвольное сочетание имен индексированных полей таблицы. Имена полей разделяются точкой с запятой. Свойства IndexName и IndexFieldNames нельзя использовать одновременно.
Число полей, используемых в текущем индексе табличного компонента, возвращает свойство
property IndexFieldCount: Integer;
свойство
property IndexFields: [Index: Integer]: TField;
представляет собой индексированный список полей, входящих в текущий индекс.
Параметры созданных индексов доступны в свойстве
property IndexDefs: TIndexDefs;
Класс TIndexDefs подробно рассматривается в гл. 12.
После создания и подключения индекса записи набора данных "переупорядочиваются" в соответствии со значениями индексированных долей.
Удаление локального индекса обеспечивает метод
procedure Deletelndex(const Name: string);
После удаления текущего индекса или его отмены (обнуления свойства IndexName) записи набора данных "переупорядочиваются" в исходном порядке, соответствующем порядку записей набора данных на сервере.
Имена всех существующих в наборе данных индексов можно загрузить в список при помощи метода
procedure GetlndexNames(List: TStrings);
Например:
Memol.Lines.Clear;
ClientDataSet.GetlndexNames(Memol.Lines);
Кэширование и редактирование данных
После получения записей от провайдера набор данных сохраняется в локальном буфере памяти. И все вносимые изменения после применения метода Post также сохраняются локально и не пересылаются на сервер. Буфер изменений доступен при помощи свойства
property Delta: OleVariant;
Для передачи изменений на сервер используется метод
function ApplyUpdates(MaxErrors: Integer);
Integer; virtual;
где параметр MaxErrors задает число ошибок, которые игнорируются при сохранении данных на сервере. Если параметр равен —1, сохранение на сервере прерывается при первой же ошибке. Метод возвращает число сохраненных записей.
После выполнения метода ApplyUpdates все записи, сохранить которые не удалось, возвращаются клиенту в локальный буфер Delta.
Если клиентское приложение будет редко изменять свои наборы данных, сохранение изменений на сервере можно связать с методом-обработчиком
AfterPost:
procedure TForml.ClientDataSetAfterPost(DataSet: TDataSet);
begin
ClientDataSet.ApplyUpdates(-1);
end;
Свойство только для чтения
property ChangeCount: Integer;
возвращает общее число изменений, содержащееся в буфере Delta. Для очистки буфера изменений используется метод
procedure CancelUpdates;
После вызова метода свойство ChangeCount принимает значение 0.
До и после сохранения изменений на сервере соответственно вызываются методы-обработчики
property BeforeApplyUpdates: TRemoteEvent;
property AfterApplyUpdates: TRemoteEvent;
Несмотря на сделанные локально многократные изменения, запись может быть восстановлена в первоначальном виде. Метод
procedure RefreshRecord;
получает от провайдера первоначальный вариант текущей записи, сохраненный на сервере.
При этом (и при всех других случаях, когда компонент запрашивает обновление текущей записи) вызываются методы-обработчики
property BeforeRowRequest: TRemoteEvent;
property AfterRowRequest: TRemoteEvent;
Но что делать, если необходимо восстановить удаленную запись? В обычном наборе данных после сохранения это невозможно. В компоненте TClientDataSet существует метод
function UndoLastChange(FollowChange: Boolean): Boolean;
который возвращает набор данных к состоянию до последней выполненной операции редактирования, добавления или удаления записи. Если параметр FollowChange имеет значение True, курсор набора данных будет установлен на восстановленную запись.
О состоянии текущей записи позволяет судить метод
function UpdateStatus: TUpdateStatus; override;
который возвращает значение типа
TUpdateStatus = (usUnmodified, usModified, uslnserted, usDeleted);
означающее состояние текущей записи:
usUnmodified — запись осталась неизменной;
usModified — запись была изменена;
uslnserted — запись была добавлена;
usDeleted — запись была удалена.
Например, при закрытии набора данных можно выполнить проверку:
if ClientDataSet.UpdateStatus = usModified
then ShowMessage('Record was changed');
На основе типа можно управлять видимостью записей в наборе данных. Свойство
property StatusFilter: TUpdateStatusSet;
определяет, какой тип записей будет отображаться в наборе данных. Например:
ClientDataSet.StatusFilter := usDeleted;
отобразит в наборе данных только удаленные записи (при этом изменения не сохранены на сервере).
Клиент многозвенного распределенного приложения
Клиентское ПО в распределенном многозвенном приложении имеет особенности архитектуры, продиктованные его ролью — ведь большая часть бизнес-логики и функций обработки данных сосредоточены в сервере приложений (см. гл. 21). Такая схема призвана обеспечить более высокую эффективность обработки запросов многочисленных удаленных клиентов, а также упрощает обслуживание клиентского ПО. Клиенты, выполняющие лишь необходимый минимум операций, называются "тонкими".
Клиенты многозвенных приложений обеспечивают выполнение следующих функций:
соединение с сервером приложений, прием и передача данных; отображение средствами пользовательского интерфейса; простейшие операции редактирования; сохранение локальных копий данных.При разработке клиентских частей многозвенных приложений в Delphi используются компоненты DataSnap (см. гл. 20), а также компонент TClientoataSet, роль которого трудно переоценить.
Помимо новых компонентов в процессе разработки применяются стандартные компоненты отображения данных, подробно рассматриваемые в гл. 15, а также обычная схема связывания визуальных компонентов с набором данных через компонент TDataSource (см. гл. 11).
В этой главе рассматриваются следующие вопросы:
структура клиентского приложения; соединение удаленного клиента с сервером приложений; набор данных клиента в компоненте TdientoataSet, локальное кэширование данных; основные операции обработки данных, выполняемые клиентским набором данных; вложенные наборы данных; обработка локальных ошибок клиентского набора данных и ошибок сервера приложений.Клиентские наборы данных
В Палитре компонентов Delphi представлено несколько компонентов, инкапсулирующих клиентский набор данных. В то же время при разработке настоящих удаленных клиентских приложений применяется компонент TClientDataSet. Внесем ясность в этот вопрос. Итак, помимо компонента TClientDataSet, расположенного на странице Data Access, существуют еще два компонента:
TSimpleDataSet — разработан для технологии доступа к данным dbExpress и, по существу, является единственным полноценным средством для работы с набором данных в рамках этой технологии; TiBdientDataSet — используется в технологии доступа к данным сервера InterBase — InterBase Express.Все перечисленные компоненты произошли от общего предка — класса TCustomClientoataSet (рис. 22.2). Они обеспечивают локальное кэширование данных и взаимодействие с серверным набором данных при посредстве интерфейса IProviderSupport.
Основное различие между компонентом TClientDataSet и другими клиентскими компонентами заключается в том, что первый предназначен для использования с внешним компонентом-провайдером данных. А значит, он может взаимодействовать с удаленным провайдером данных.
Остальные перечисленные компоненты инкапсулируют внутренний провайдер данных, предоставляя тем самым для использования в рамках соответствующих технологий доступа к данным эффективный механизм локального кэширования данных. Использование внутреннего провайдера данных обеспечивает общий класс- предок TCustomCachedDataSet.
Рис. 22.2. Иерархия классов клиентских наборов данных
Для этого он имеет защищенное свойство
property Provider: TDataSetProvider;
Соединение с источником данных осуществляется не свойством RemoteServer (будет рассмотрено ниже применительно к компоненту TclientDataSet). задающим удаленный сервер, а стандартными средствами соответствующей технологии доступа к данным.
Таким образом, для работы с удаленными данными (т. е. внешними по отношению к клиенту) пригоден только компонент TclientDataSet, умеющий работать с внешним провайдером данных.
Компонент TClientDataSet
Компонент TclientDataSet используется в клиентской части многозвенного распределенного приложения. Он инкапсулирует набор данных, переданный при помощи компонента-провайдера из удаленного набора данных. Компонент обеспечивает выполнение следующих основных функций:
получение данных от удаленного сервера и передача ему сделанных изменений с использованием удаленного компонента-провайдера; представление набора данных при помощи локального буфера и поддержка основных операций, унаследованных от класса TDataSet; объединение записей набора данных при помощи агрегатных функций для получения суммарных данных; локальное сохранение набора данных в файле и последующее восстановление набора данных из файла; представление набора данных в формате XML.Предком компонента TclientDataSet является класс TDataSet, поэтому TclientDataSet обладает таким же набором функций, что и обычный компонент, инкапсулирующий набор данных. Основное же отличие заключается в том, источник данных для него доступен только через удаленный компонент-провайдер. Это означает, что сохранение изменений и обновление набора данных осуществляется локально, без обращения к источнику данных.
Например, выполнение метода Post приведет лишь к сохранению текущей записи набора данных в локальном кэше. Все изменения отсылаются на сервер только при необходимости и легко управляются разработчиком.
Как и обычный компонент, компонент TclientDataSet может использоваться совместно с визуальными компонентами отображения данных. Для этого нужен компонент TDataSource.
Рассмотрим основные функции, реализуемые компонентом TclientDataSet.
Наборы данных клиентского приложения
Каждый из компонентов TClientDataSet модуля данных DataModuie связан с соответствующим компонентом-провайдером сервера.
Компонент cdsorders предназначен для просмотра данных о заказах. Вспомогательные компоненты cdsEmpioyees и cdsCustomers содержат списки заказчиков и работников, используемые в главном наборе данных. В компоненте cdsorders определено агрегатное поле Paidsum, рассчитывающее сумму платежей по всем заказам.
Компонент cdsParts предназначен для просмотра и редактирования данных о поступлениях. Компонент cdsvendors представляет список поставщиков. Так как в сервере приложения связанный с cdsvendors набор данных является главным в отношении "один-ко-многим". то одновременно с обычными полями для компонента cdsvendors автоматически создается поле tbiParts типа TDataSetField. Это поле позволяет настроить вложенный набор данных. Для этого достаточно в свойстве DataSetField вложенного компонента cdsParts задать поле tbiParts. Теперь при перемещении по записям главного набора данных cdsvendors вложенный набор данных компонента cdsParts будет отображать записи, связанные с текущим поставщиком.
Примечание
В целях сохранения простоты и наглядности исходного кода редактирование предусмотрено только для одного компонента cdsParts. В реальной работе аналогичные методы могут использоваться для всех наборов данных.
Для компонента cdsParts созданы два агрегата, суммирующие данные о поступлениях и продажах. При перемещении по записям этого набора данных в методе-обработчике AfterScroll предусмотрено обновление значений агрегатов (см. листинг 22.1).
Так как компонент cdsParts предназначен и для редактирования данных, то для него необходимо предусмотреть обработку исключительных ситуаций, возникающих не только на клиенте, но и на сервере. Для этого используется метод-обработчик cdsPartsReconclieError (см. листинг 22.1). Сама операция очень проста и скорее служит лишь демонстрацией возможности создавать собственную обработку серверных исключений вместо использования стандартной функции HandleReconclieError (см. рис. 22.4). Здесь все изменения в проблемных записях отменяются методом cancelupdates и выводится сообщение об ошибке.
Локальное редактирование, сохранение или отмена изменений для компонента cdsParts выполняется стандартными методами набора данных (см. гл. 12). Дополнительно при отмене изменений используется метод undoLastchange, позволяющий полностью восстановить последнюю модифицированную запись даже после локального сохранения изменений.
Для передачи изменений серверу использован метод ApllyUpdates. Параметр -1 означает, что клиенту будет возвращено сообщение о первой же ошибке.
Объекты-агрегаты
Для вычисления агрегатных выражений для всех записей набора данных используются объекты класса TAggregate. Индексированный список этих объектов содержится в свойстве
property Aggregates: TAggregates;
компонента TClientDataSet. Прямым предком класса TAggregates является класс TCollection, поэтому для него можно использовать все основные приемы работы с коллекциями (см. гл. 7).
Для создания нового агрегата необходимо щелкнуть на кнопке свойства в Инспекторе объектов и, в появившемся Редакторе агрегатов, выбрать пункт Add во всплывающем меню или щелкнуть на кнопке Add New (рис. 22.3).
Новый агрегат может быть добавлен и динамически:
var NewAgg: TAggregate;
NewAgg := ClientDataSet.Aggregates.Add;
Рис. 22.3. Редактор агрегатов компонента TClientDataSet
Рассмотрим свойства класса TAggregate.
Имя агрегата содержится в свойстве
property AggregateName: string;
которое может быть использовано при отображении агрегата в визуальных компонентах.
Вычисляемое выражение с применением агрегатных функций должно находиться в свойстве
property Expression: String;
Например, для таблицы COUNTRY.DB из демонстрационной базы данных Delphi можно вычислять общую площадь государств Северной и Южной Америки (площадь государства содержится в поле Area):
ClientDataSet.Aggregates[Somelndex].Expression := 'SUM(Area)';
Вычислением агрегата управляет свойство
property Active: Boolean;
а вычисленное значение возвращает функция
function Value: Variant;
Если пользователь редактирует набор данных, то для всех включенных агрегатов (Active = True) возвращаемое значение автоматически пересчитывается.
Например, после сохранения изменений в наборе данных можно визуализировать новое значение агрегата:
SomeLabel.Caption := ClientDataSet.Aggregates[0].AggregateName;
SomeEdit.Text := ClientDataSet.Aggregates[0].Value;
Для проверки активности агрегата, помимо проверки значения свойства Active, можно также использовать свойство
property InUse: Boolean;
Если оно возвращает значение True — вычисляемое выражение агрегата рассчитывается.
Видимость агрегата в визуальных компонентах управляется свойством
property Visible: Boolean;
Для того чтобы снизить вычислительную нагрузку на набор данных, можно отключить все агрегаты одновременно. Для этого свойству
property AggregatesActive: Boolean;
необходимо присвоить значение False.
Если же AggregatesActive = True, вычисляются только активные агрегаты, для которых свойство Active имеет значение True.
Если вам необходимо использовать все активные агрегаты, то вместо их последовательного перебора с проверкой свойства Active можно использовать свойство
property ActiveAggs[Index: Integer] : TList;
компонента TClientDataSet, которое представляет собой список активных агрегатов.
Обработка ошибок
Особенности использования компонента TClientDataSet распространяются также и на обработку ошибок. Ведь клиентский набор данных должен реагировать не только на ошибки, возникшие локально, но и на ошибки сохранения изменений на сервере.
В первом случае разработчик может применить стандартные способы. Это использование блоков try..except или методов обработчиков, унаследованных от класса TDataSet:
property OnDeleteError: TDataSetErrorEvent; — вызывается при ошибках удаления записей; property OnEditError: TDataSetErrorEvent; — вызывается при ошибках редактирования записей; property OnPostError: TDataSetErrorEvent; — вызывается при ошибках локального сохранения записей.Все они используют процедурный тип
type
TDataSetErrorEvent = procedure(DataSet: TDataSet;
E: EDatabaseError;
var Action: TDataAction) of object;
Здесь, помимо параметров DataSet и Е, определяющих соответственно набор данных и тип ошибки, параметром Action можно задать вариант реакции на ошибку:
type TDataAction = (daFail, daAbort, daRetry);
daFail — прервать операцию и показать сообщение об ошибке;
daAbort — прервать операцию без сообщения об ошибке;
daRetry — повторить операцию
Например, при возникновении ошибки редактирования набора данных код обработчика может выглядеть следующим образом:
procedure TForml.ClientDataSetEditError(DataSet: TDataSet;
E: EDatabaseError; var Action: TDataAction);
begin
if Not (DataSet.State in [dsEdit, dslnsert]) then
begin
DataSet.Edit; Action := daRetry;
end
else Action := daAbort;
end;
Здесь, если набор данных не находится в состоянии редактирования, это упущение исправляется и операция повторяется.
Итак, с локальными ошибками все обстоит достаточно просто. А как клиентский набор данных "узнает" об ошибке на удаленном сервере? Очевидно, при помощи своего компонента-провайдера. Действительно, компонент TDataSetProvider не только возвращает клиенту несохраненные изменения в пакете Delta (см. выше), но и обеспечивает генерацию события, реакцией на которое является метод-обработчик
type
TReconcileErrorEvent = procedure(DataSet: TCustomClientDataSet; E: EReconcileError;
UpdateKind: TUpdateKind;
var Action:
TReconcileAction) of object;
property OnReconcileError: TReconcileErrorEvent;
Обратите внимание, что все параметры похожи на соответствующие параметры локальных обработчиков, но имеют собственные типы. Рассмотрим их.
Параметр UpdateKind содержит указание на тип операции, вызвавшей ошибку на сервере:
type
TUpdateKind = (ukModify, uklnsert, ukDelete);
ukModify — изменение данных;
uklnsert — добавление записей;
ukDelete — удаление записей.
Параметр Action позволяет разработчику предусмотреть реакцию клиентского набора данных на ошибку:
type-
TReconcileAction = (raSkip, raAbort, raMerge, raCorrect, raCancel, raRefresh);
raSkip — отменить операцию для записей, вызвавших ошибку, с их сохранением в буфере;
raAbort — отменить все изменения для операции, вызвавшей ошибку;
raMerge — совместить измененные записи с аналогичными записями сервера;
racorrect — сохранить изменения, сделанные в данном методе-обработчике;
racancel — отменить изменения, вызвавшие ошибку, заменив их исходными локальными значениями клиентского набора данных;
raRefresh — отменить изменения, вызвавшие ошибку, заменив их исходными значениями серверного набора данных.
Как видите, выбор возможных реакций на ошибку сервера несколько шире, чем на локальные ошибки.
Тип ошибки возвращается параметром Е, для которого предусмотрен специальный класс EReconcileError, имеющий несколько полезных свойств.
Свойство
property ErrorCode: DBResult;
возвращает код ошибки. Используемые коды ошибок можно найти в файле \Source\Vcl\DSIntf.pas. Код предыдущей ошибки возвращается свойством property PreviousError: DBResult;
Рис. 22.4. Стандартный диалог обработки ошибок сервера
Используя представленную здесь информацию, вы можете самостоятельно управлять обработкой ошибок сервера на клиенте. Но можно поступить и более просто — использовать стандартный диалог обработки удаленных ошибок (рис. 22.4). Этот диалог можно подключить к вашему проекту (он содержится в модуле \ObjRepos\RecError.pas) и вызвать при помощи процедуры:
function HandleReconcileError(DataSet: TDataSet; UpdateKind: TUpdateKind; ReconcileError: EReconcileError): TReconcileAction;
В параметры этой функции подставляются параметры метода-обработчика OnReconciieError, а возвращает данная функция действие, выбранное пользователем в диалоге (см. рис. 22.4). Таким образом, ее использование очень просто:
procedure TForml.ClientDataSetReconcileError(DataSet: TCustomClientDataSet;
E: EReconcileError; UpdateKind: TUpdateKind;
var Action: TReconcileAction);
begin
Action := HandleReconcileError(DataSet, UpdateKind, E) ; end;
Получение данных от компонента - провайдера
Компонент TClientDataSet получает доступ к удаленным данным через компонент соединения DataSnap (см. гл. 20). В зависимости от используемой технологии, это могут быть технологии TDCOMConnection, TSocketConnection, TWebConnection ИЛИ TCorbaConnection.
Компонент TClientDataSet связывается с компонентом соединения при помощи свойства
property RemoteServer: TCustomRemoteServer;
Если соединение настроено правильно, то ссылка на интерфейс IAppServer в свойстве
property AppServer: IAppServer;
совпадает со свойством
ClientDataSet.RemoteServer.AppServer;
После настройки соединения в свойстве
property ProviderName: string;
можно выбрать один из компонентов-провайдеров, которые доступны на сервере приложений, выбранном в компоненте соединения.
Если провайдер был подключен правильно, свойство только для чтения
property HasAppServer: Boolean;
автоматически принимает значение True.
Теперь компонент готов к приему данных. При использовании метода
procedure Open;
или свойства
property Active: Boolean;
компонент получает от провайдера первый пакет данных.
Размер пакета определяется свойством
property PacketRecords: Integer;
которое задает число записей, передаваемое в одном пакете. Если свойство имеет значение —1 (это значение по умолчанию), передаются все записи набора данных. Если оно равно 0 — клиенту передаются только метаданные о наборе данных.
Если соединение клиента с сервером медленное, число записей в пакете можно уменьшить, но желательно так, чтобы при использовании компонентов TDBGrid полученные в одном пакете записи полностью заполняли рабочую область этого компонента.
Одновременно разработчик имеет возможность управлять доставкой следующих пакетов. Для этого используется метод
function GetNextPacket: Integer;
Например, это можно сделать следующим образом:
procedure TDataModulel.ClientDataSetAfterScroll(DataSet: TDataSet);
begin
if ClientDataSet.EOF then ClientDataSet.GetNextPacket; end;
Свойство
property FetchOnDemand: Boolean;
должно иметь значение False. При значении True оно разрешает компоненту получать новые пакеты данных по мере надобности, например, при необходимости прокрутки записей в компоненте TDBGrid.
До и после получения очередного пакета соответственно выполняются обработчики событий:
type
TRemoteEvent = procedure(Sender: TObject;
var OwnerData: OleVariant) of object;
property BeforeGetRecords: TRemoteEvent;
property AfterGetRecords: TRemoteEvent;
Содержимое очередного пакета представлено свойством
property Data: OleVariant;
Данные в нем хранятся в транспортном формате, готовые для пересылки. Причем его можно использовать не только для чтения, но и для записи, формируя пакет данных для отправки провайдеру:
var OwnerData: OleVariant;
MaxErrors, ErrorCount: Integer;
MaxErrors := 0;
ResultDataSet.Data := SourceDataSet.AppServer.AS_ApplyUpdates('', SourceDataSet.Delta, MaxErrors, ErrorCount, OwnerData);
Метод AS_AppiyUpdates передает данные, содержащиеся в буфере Delta, провайдеру на сервер и возвращает записи, сохранить которые не удалось. Подробнее о методе AS_ApplyUpdates см. табл. 21.1.
Размер буфера Data в байтах возвращает свойство
property DataSize: Integer;
Представление данных в формате XML
Набор данных клиента легко можно представить в формате XML. Для этого достаточно использовать свойство
property XMLData: OleVariant;
которое возвращает данные, содержащиеся в буфере Data (см. выше) в бинарном виде, в формате XML.
Например, клиентский набор данных можно сохранить в файле формата XML:
if SaveDialog.Execute then
with TFileStream.Create(SaveDialog.FileName, fmCreate) do
try
Write(Pointer(ClientDataSet.XMLData)^, Length(ClientDataSet.XMLData));
finally
Free ;
end;
Пример "тонкого" клиента
Пример клиентского приложения является частью группы проектов SimpleRemote.bpg и предназначен для взаимодействия с сервером приложений simpleAppSrvr (рис. 22.5), процесс создания которого подробно рассматривался в гл. 21.
Рис. 22.5. Окно клиентского приложения Simple Client
Проект клиента Simple Client состоит из двух файлов.
Компоненты, обеспечивающие соединение с удаленным сервером приложения и работу с наборами данных, сосредоточены в модуле данных DataModule (файл uDataModule.pas). Обратите внимание, что это "обычный" модуль данных, используемый в приложениях баз данных (см. гл. 11). Главная форма клиентского приложения fmMain (файл uMain.pas), содержащая визуальные компоненты пользовательского интерфейса.ЛИСТИНГ 22.1 .Секция implementation модуля данных DataModule
implementation
uses uMain, Variants, Dialogs;
{$R *.dfm}
procedure TDM.SrvrConAfterConnect(Sender: TObject);
var i: Integer;
begin
for i := 0 to SrvrCon.DataSetCount - 1 do
SrvrCon.DataSets[i].Open;
cdsVendors.Open;
end;
procedure TDM.SrvrConBeforeDisconnect(Sender: TObject);
var i: Integer;
begin
for i := 0 to SrvrCon.DataSetCount - 1
do SrvrCon.DataSets[i].Close;
cdsVendors.Close;
end;
procedure TDM.cdsVendorsAfterScroll(DataSet: TDataSet);
begin
fmMain.edCostSum.Text := VarToStr(cdsParts.Aggregates[0].Value);
fmMain.edPriceSum.Text := VarToStr(cdsParts.Aggregatesfl].Value);
end;
procedure TDM.cdsPartsReconcileError(DataSet: TCustomClientDataSet;
E: EReconcileError; UpdateKind: TUpdateKind; var Action: TReconcileAction);
begin
cdsParts.CancelUpdates;
MessageDlg(E.Message, mtError, [mbOK], 0);
end;
Работа с данными типа BLOB
Если набор данных сервера содержит большие поля (например, изображения), передача данных по медленному каналу займет очень много времени,
что, несомненно, снизит эффективность приложения. Простейшее решение проблемы — передача клиенту данных типа BLOB только в том случае, когда это ему действительно необходимо — т. е. исключительно по его запросу.
В компоненте TCHentDataSet процессом передачи полей типа BLOB можно управлять, используя свойство
property FetchOnDemand: Boolean;
По умолчанию оно равно значению True и клиентский набор данных "выкачивает" данные BLOB по мере необходимости автоматически. Это означает, что приложение будет останавливаться и заново получать данные при любом просмотре данных, прокрутке и т. д. Если свойство имеет значение False, для получения данных клиент должен явно вызвать метод
procedure FetchBlobs;
Но, кроме этого, в свойстве options компонента-провайдера TDataSetProvider обязательно должно быть установлено значение:
poFetchBlobsOnDemand := True;
делегирующие большинство функций ПО промежуточного
В многозвенных распределенных приложениях в основном используются "тонкие" клиенты, делегирующие большинство функций ПО промежуточного слоя. В трехзвенных приложениях — это сервер приложений.
Основой клиентского приложения является компонент TCLientoataSet, который инкапсулирует набор данных и обеспечивает его использование при помощи локального буфера. Соединение с удаленным сервером приложений осуществляется при помощи компонентов DataSnap.
Соединение клиента с сервером приложения
Для соединения клиентского приложения с сервером в локальной сети использован компонент srvrCon класса TDCOMConnection. Данный тип соединения выбран как наиболее простой и требующий лишь наличия локальной сети или даже не требующий ничего — в демонстрационном приложении можно использовать сервер приложения, установленный на этом же компьютере.
Для настройки соединения компонента SrvrCon в свойстве ComputerName было указано имя компьютера сервера. После этого в списке свойства ServerName можно выбрать один из доступных зарегистрированных серверов. В нашем случае это сервер simpieAppSrvr.simpieRDM, имя которого состоит из имени приложения сервера и имени главного удаленного модуля данных.
Обратите внимание, что в этом же списке имеется и дочерний модуль Secondary. Однако для получения доступа к наборам данных дочернего модуля данных мы не будем создавать еще одно соединение, а воспользуемся компонентом TSharedConnection, т. к. он специально предназначен для подобных случаев. Для его настройки достаточно указать в свойстве Parentconnection компонент соединения. В нашем случае — это srvrCon.
Для компонента srvrCon предусмотрены два метода-обработчика (см. листинг 22.1) — после подключения и перед отключением соединения. В них открываются и закрываются все наборы данных клиентского приложения.
Теперь в клиентском приложении доступны наборы данных обоих удаленных модулей данных сервера приложений.
Непосредственно подключение к серверу осуществляется кнопкой Соединение. При ее нажатии выполняется следующий простой код:
procedure TfmMain.tbConnectClick(Sender: TObject);
begin
try
DM.SrvrCon.Close;
DM.SrvrCon.ComputerName := edServerName.Text; DM.SrvrCon.Open;
except
on E: Exception do MessageDlg(E.Message, mtError, [mbOK], O);
end;
SetCtrlState;
end;
-Соединение закрывается, задается новое имя компьютера сервера, соединение открывается. Специально созданный метод формы setctristate управляет доступностью кнопок формы, анализируя текущее состояние наборов данных.
Сохранение набора данных в файлах
Клиентское приложение может использовать одну очень удобную функцию компонента TClientDataSet. Представим, что соединение между сервером и клиентом обладает малой пропускной способностью и к тому же часто обрывается. Что в этом случае делать пользователю, который внес много изменений и не может сохранить их на сервере?
В этом случае можно сохранить набор данных клиента в файле на локальном диске, а при удобной возможности — загрузить обратно и переслать на сервер.
Для сохранения данных (по существу это буфер Data) в файле используется метод
procedure SaveToFile(const FileName: string = ''; Format: TDataPacketFormat=dfBinary);
Причем, если параметр FileName пуст, имя файла берется из свойства
property FileName: string;
Также можно передать данные в поток:
procedure SaveToStream(Stream: TStream;
Format: TDataPacketFormat=dfBinary);
Формат, в котором данные будут сохранены, определяется параметром
Format!
type TDataPacketFormat = (dfBinary, dfXML, dfXMLUTFS);
где dfBinary — бинарный вид, dfXML — формат XML, dfXMLUTFS — формат XML в кодировке UTF8.
Обратная загрузка данных, соответственно, выполняется методами:
procedure LoadFromFile(const FileName: string = '');
и
procedure LoadFromStreamfStream: TStream);
После загрузки набор данных полностью готов к работе:
if LoadFileDialog.Execute then
begin
ClientDataSet.LoadFromFile
(LoadFileDialog.FileName);
ClientDataSet.Open;
end;
Структура клиентского приложения
По своей структуре (рис. 22.1) клиентское приложение подобно обычному приложению баз данных, рассматриваемому в гл. П.
Рис. 22.1. Структура клиентской части многозвенного приложения Delphi
Соединение клиента с сервером приложений осуществляется специализированными компонентами DataSnap (см. гл. 20). Эти компоненты взаимодействуют с удаленным модулем данных, входящим в состав сервера, при помощи методов интерфейса IAppServer.
Также в клиентском приложении могут использоваться дополнительные, определенные разработчиком, методы интерфейса удаленного модуля данных, унаследованного от интерфейса IAppServer. Подробнее об этих компонентах и способах их настройки на удаленный сервер приложений см. гл. 21.
Внимание
Соединение с сервером приложений обеспечивает динамическая библиотека MIDAS.DLL, которая должна быть зарегистрирована на компьютере клиента.
Как и обычное приложение БД, клиент многозвенного распределенного приложения должен содержать компоненты, инкапсулирующие набор данных, которые связаны с визуальными компонентами отображения данных посредством компонентов TDataSource.
Очевидно, что набор данных сервера должен быть скопирован клиентским приложением в некий локальный буфер. При этом должен использоваться эффективный механизм загрузки данных сравнительно небольшими порциями, что позволяет значительно разгрузить транспортный канал между клиентом и сервером приложений.
Кэширование и редактирование данных в клиентском приложении обеспечивает специализированный компонент TclientDataSet, отдаленным предком которого является класс TDataSet. Помимо унаследованных от предков методов, класс TclientDataSet инкапсулирует ряд дополнительных функций, облегчающих управление данными.
Примечание
Подобно обычному приложению БД, в "тонком" клиенте для размещения невизуальных компонентов доступа к данным необходимо использовать модули данных.
Для получения набора данных сервера компонент TclientDataSet взаимодействует с компонентом TDataSetProvider, используя методы интерфейса IProviderSupport (см. гл. 21).
По существу все уникальные функции клиентского приложения сосредоточены в компоненте TclientDataSet, изучением которого мы и займемся далее в этой главе. В остальном клиентское приложение не отличается от обычного приложения БД и при его разработке могут применяться стандартные методы.
Управление запросом на сервере
Компонент TdientDataSet может не только эффективно управлять своим набором данных, но и влиять на выполнение серверного компонента, с которым он связан через провайдер.
Свойство
property CornmandText: string;
содержит текст запроса SQL, имя таблицы или хранимой процедуры в зависимости от типа серверного компонента.
Изменив значение этого свойства на клиенте, можно, например, модифицировать запрос SQL на сервере. Но для этого в свойстве Options соответствующего компонента-провайдера TDataSetProvider должно быть установлено значение
poAliowCommandText := True;
Новое значение свойства CommandText отправляется на сервер только после открытия клиентского набора данных или выполнения метода
procedure Execute; virtual;
Для запросов или хранимых процедур можно задавать параметры, которые сохраняются в свойстве
property Params: TParams;
До выполнения запроса присваиваются значения входным параметрам. После выполнения хранимой процедуры в выходных параметрах размещаются полученные от сервера значения.
Обратите внимание, что при выполнении запросов или хранимых процедур может измениться порядок следования параметров. Поэтому обращаться к параметрам желательно по их именам. Например, так:
Editl.Text := ClientDataSet.Params .ParamByName('OutputParam') .AsString;
Для того чтобы получить текущие значения параметров компонента набора данных на сервере, достаточно использовать метод
procedure FetchParams;
Перед и после получения параметров от провайдера, клиентский набор данных вызывает методы-обработчики событий:
property BeforeGetParams: TRemoteEvent;
property AfterGetParams: TRemoteEvent;
Вложенные наборы данных
В гл. 14 рассматривался вопрос организации между таблицами отношения "один-ко-многим", когда через одинаковое значение поля внешнего ключа одна запись главной таблицы связывается с несколькими записями подчиненной таблицы. Этот широко распространенный в практике программирования приложений БД механизм реализован и в компоненте TClientDataSet.
Для этого используется класс поля TDataSetField.
На стороне клиента для создания отношения "один-ко-многим" необходимо использовать как минимум два компонента TClientDataSet, главный из которых инкапсулирует основной набор данных, а подчиненный — вложенный набор данных.
Итак, на стороне сервера есть два табличных компонента, связанных отношением "один-ко-многим" при помощи свойств MasterSource и MasterFields (см. гл. 14). Также это могут быть и два компонента запросов SQL, связанные параметрами подчиненного запроса с одноименными полями главного запроса и свойством DataSource.
Теперь на стороне клиента необходимо при помощи компонента-провайдера связать компонент TClientDataSet с главным серверным компонентом отношения "один-ко-многим" и создать для него статические объекты для всех полей. Для этого достаточно дважды щелкнуть на компоненте и в окне Редактора полей (см. рис. 22.3) из всплывающего меню выбрать пункт Add Field. В результате в окне Редактора полей появятся имена объектов для
всех полей серверного набора данных, а также еще одно дополнительное поле объектного типа TDataSetFieid. Его имя совпадает с именем подчиненного серверного компонента отношения "один-ко-многим".
Это поле связано с подчиненным компонентом на сервере. Чтобы убедиться в этом, достаточно просмотреть значение его свойства только для чтения
property NestedDataSet: TDataSet;
Индексированный список всех полей, передаваемых из серверного подчиненного компонента, содержится в свойстве только для чтения
property Fields: TFields;
В дальнейшем связь между компонентами на клиенте настраивается именно через это поле. В подчиненном компоненте TClientDataSet в Инспекторе объектов необходимо выбрать свойство
property DataSetField: TDataSetFieid;
В списке этого свойства вы увидите имя только что созданного поля объектного типа TDataSetField. Выберите его и отношение "один-ко-многим" для клиентских наборов данных готово. При этом в компоненте вложенного набора данных автоматически очищаются свойства RemoteServer и FroviderName, т. к. их значения утрачивают значение и компонент оказывается связан только с главным компонентом отношения "один-ко-многим".
Теперь при навигации по записям основного набора данных во вложенном наборе данных автоматически будут появляться связанные записи. Также вы можете использовать все возможности, предоставляемые компонентом TClientDataSet как для основного, так и для вложенного набора данных.
Компоненты Rave Reports и отчеты в приложении Delphi
в качестве основного средства для
Генератор отчетов Rave Reports 5.0 разработан фирмой Nevrona и входит в состав Delphi 7 в качестве основного средства для создания отчетов. Он состоит из трех частей:
ядро генератора отчетов обеспечивает управление отчетом и его предварительный просмотр, и отправку на печать. Исполняемый код ядра сервера включается в приложение Delphi, делая его полностью автономным при работе с отчетами на компьютере клиента; визуальная среда разработки отчетов Rave Reports предназначена для разработки самих отчетов. Она позволяет добавлять к отчету страницы, размешать на них графические и текстовые элементы управления, подключать к отчетам источники данных и т. д. Отчеты сохраняются в файлах с расширением rav и должны распространяться совместно с приложениями, использующими их; компоненты Rave Reports расположены на странице Rave Палитры компонентов Delphi. Они обеспечивают управление отчетами в приложении. Генератор отчетов устанавливается при инсталляции Delphi в папку \Delphi7\Rave5. Исходные коды компонентов разработчикам в Delphi недоступны.
Безусловно, визуальная среда разработки заметно упрощает процесс создания отчетов и позволяет добиться лучших результатов меньшими усилиями, чем в генераторе отчетов Quick Report, который использовался в предыдущих версиях Delphi. Тем не менее при первом знакомстве с продуктом заметны и его недостатки. Система помощи оставляет тягостное впечатление не только своей крайней лаконичностью, но и фактическими ошибками. Многие свойства и методы остались недокументированными, и наоборот — имеющиеся в статьях подсказки описания не имеют реальных аналогов в коде компонентов.
Однако будем надеяться, что недостатки будут со временем исправлены. А мы займемся детальным знакомством с процессом создания отчетов.
Компонент отчета TRvProject
Компонент TRvProject обеспечивает представление в приложении отчета. Для того чтобы связать проект отчета Rave Reports с компонентом, используется свойство
property ProjectFile: string;
До начала печати необходимо связать компонент TRvProject с компонентом управления отчетом TRvSystem. Для этого достаточно передать в свойстве
property Engine: TRpComponent;
ссылку на компонент TRvSystem.
При необходимости вы можете загрузить отчет из внешнего файла или потока:
procedure LoadFromFile(FileName: String);
procedure LoadFromStream(Stream: TStream);
Загруженный отчет становится текущим.
Кроме этого существует и пара методов для сохранения отчета:
function SaveToFile(FileName: String);
procedure SaveToStream(Stream: TStream);
В процессе работы приложения может потребоваться напечатать несколько различных отчетов. Для этого можно использовать требуемое число компонентов TRvProject или загружать нужные отчеты по мере необходимости.
Забегая немного вперед (см. гл. 24), скажем, что один файл проекта отчета может содержать несколько независимых отчетов. Каждый из них идентифицируется в компоненте TRvProject тремя свойствами. Имя, полное имя и описание отчета содержатся соответственно в трех свойствах только для чтения:
property ReportName: String;
property ReportFullName: String;
property ReportDesc: String;
При этом эти три свойства возвращают параметры текущего отчета. Сразу после загрузки из файла текущим становится отчет, являющийся отчетом по умолчанию в среде разработки. При необходимости сменить текущий отчет используется метод
function SelectReport(ReportName: string; FullName: boolean): boolean;
В параметре ReportName передается имя нужного отчета. Если параметр FullName имеет значение True, то это полное имя отчета, иначе — имя отчета.
В случае, если проект содержит несколько отчетов, их имена доступны при помощи метода
procedure GetReportList(ReportList: TStrings;FullName: boolean);
Список имен будет возвращен в список строк ReportList, а параметр FullName определяет, какие именно имена будут занесены в список. При значении параметра True метод возвращает полные имена отчетов (соответствует свойству ReportFullName), иначе — имена (соответствует свойству ReportlName).
Например, код
var ReportList: TStringList; i: Integer;
ReportList := TStringList.Create;
RvProjectl.Open;
try
RvProjectl.GetReportList(ReportList, False);
for i := 0 to ReportList.Count - 1
do RvProjectl.ExecuteReport(ReportList[I]);
finally
RvProjectl.Close;
ReportList.Free;
end;
последовательно печатает все отчеты, входящие в состав файла проекта отчета.
Файл проекта отчета можно включить в состав исполняемого файла приложения. Для этого используется свойство
property StoreRAV: Boolean;
При щелчке на кнопке в строке этого свойства в Инспекторе объектов открывается специализированный редактор Load Into exe (рис. 23.2).
Рис. 23.2. Редактор свойства StoreRAV компонента TRvProject
Здесь можно задать файл проекта отчета. После этого в Инспекторе объектов в строке свойства storeRAv появятся дата и время загрузки проекта отчета. Это же время и дата будут сохранены в свойстве
property RaveBlobDateTime: TDateTime;
Отправить отчет на печать можно методом
procedure Execute;
или же методом
procedure ExecuteReport(ReportName: string);
который позволяет направить на печать отчет, заданный параметром ReportName. Он должен соответствовать имени отчета, хранящемуся в свойстве ReportName компонента TRvProject.
Отчет, содержащийся в компоненте Trvproject, может быть открыт для редактирования методом
procedure Open;
Не открывая отчет, вы не сможете использовать большинство свойств и методов компонента. Дело в том, что при открытии компонент загружает отчет из файла проекта или прикомпилированного кода (в случае использования свойства StoreRAV).
Сохранение и закрытие отчета соответственно выполняются методами
procedure Save; procedure Close;
Кроме этого, действия, аналогичные методам open и close, выполняются свойством
property Active: Boolean;
Если свойству присвоить значение True — отчет открывается, иначе — закрывается.
До и после открытия и закрытия отчета вызывается четверка методов-обработчиков:
property aeforeOpen: TNotifyEvent;
property AfterOpen: TNotifyEvent;
property BeforeClose: TNotifyEvent;
property AfterClose: TNotifyEvent;
Компонент управления отчетом TRvSystem
Компонент управления отчетом TRvSystem обеспечивает выполнение основных операций с отчетом из приложения. В приложении он должен быть связан с компонентом TRvProject (см. выше разд. "Компонент отчета TRvProject" данной главы). Этого вполне достаточно, чтобы компонент TRvSystem выполнил свою работу. У разработчика нет необходимости вызывать какие-либо методы компонента, чтобы направить отчет на печать.
В его составе инкапсулированы объекты, обеспечивающие вывод отчета из компонента TRvProject в один из трех системных приемников:
файл (объект класса TSystemFiler); предварительный просмотр (объект класса TSystemPreview); принтер (объект класса TSystemPrinter).За это отвечает свойство
type
TReportDest = (rdPreview, rdPrinter, rdFile);
property ReportDest: TReportDest;
которое может принимать одно из трех значений типа TReportDest.
Соответственно, для каждого типа системного приемника имеется свойство, позволяющее задать все его основные параметры.
Для вывода в файл это комплексное свойство
property SystemFiler: TSystemFiler;
Внутри него задается имя файла во вложенном свойстве
property FileName: string;
но при этом вложенное свойство
type
TStreamMode = (smMemory, smTempFile, smFile, sraUser); property StreamMode: TStreamMode;
должно иметь значение smFile.
При выводе отчета для предварительного просмотра используется экземпляр класса TSystemPreview, который доступен через свойство
property SystemPreview: TSystemPreview;
Его свойства совпадают со свойствами компонента TRvRenderPreview.
Стандартное диалоговое окно предварительного просмотра отчета Rave Reports представлено на рис. 23.3.
Рис. 23.3. Стандартное диалоговое окно предварительного просмотра компонента TRvSystem
Заголовок этого окна задается свойством
property TitlePreview: TFormatString;
Перед открытием окна предварительного просмотра вызывается метод-обработчик
property OnPreviewShow: TNotifyEvent;
За вывод отчета на печать отвечает инкапсулированный в компоненте объект типа TSystemPrinter. К нему можно обратиться при помощи свойства
property SystemPrinter: TSystemPrinter;
Его свойства совпадают со свойствами компонента TRvRenderPrinter.
Перед тем как отправить отчет одному из трех системных приемников, компонент открывает диалог настройки печати (рис. 23.4).
Компоненты Rave Reports и их назначение
Компоненты для создания отчетов и управления расположены на странице Rave Палитры компонентов. Они делятся на следующие функциональные группы.
Компонент отчета TRvproject, с точки зрения приложения, и есть отчет. Он обеспечивает загрузку заранее созданного в визуальной среде Rave Reports отчета из файла с расширением rav.Подробнее об использовании компонента TRvproject рассказывается в ниже в этой главе.
Компонент управления отчетом TRvSystem обеспечивает работу приложения с отчетом. Взаимодействуя с компонентом отчета, с одной стороны, и сервером отчета Rave Reports, с другой, этот компонент обеспечивает просмотр и печать отчетов.Подробнее об использовании компонента TRvSystem рассказывается в ниже в этой главе.
Компоненты соединения с источниками данных предназначены для подключения различных источников данных к отчетам. При этом могут использоваться технологии доступа к данным ADO, BDE, dbExpress (см. часть IV).К этой группе относятся компоненты:
TRvCustomConnection; TRvDataSetConnection; TRvTableConnection; TRvQueryConnection.Подробнее об использовании этих компонентов рассказывается в гл. 25.
Компоненты преобразования данных позволяют конвертировать отчеты из формата данных Rave Reports в другие форматы (текстовый, PDF, HTML, RTF), а также распечатывать или просматривать отчеты.К этой группе относятся компоненты:
TRvNDRWriter; TRvRenderHTML; TRvRenderPreview; TRvRenderRTF; TRvRenderPrinter; TRvRenderText. TRvRenderPDF;Подробнее об использовании компонентов преобразования данных рассказывается ниже в этой главе.
Компоненты Rave Reports и отчеты в приложении Delphi
На первый взгляд кажется, что в сфере создания и печати отчетов в Delphi 7 произошла небольшая революция. Просматривая первый раз Палитру компонентов, вы не найдете в ней хорошо знакомой по прошлым версиям Delphi страницы QReport. Вместо старого генератора отчетов в состав Delphi 7 включен продукт Rave Reports 5.0 от фирмы Nevrona. "Ну и почему же это событие не дотягивает до революции в отчетах?" — спросит читатель. Авторы могут обосновать свою точку зрения.
Во-первых, компоненты QReport по-прежнему доступны разработчику — пакет DCLQRT70.BPL все так же занимает прочное место в папке \Delphi7\Bin и может быть установлен в Палитру компонентов обычным способом. Да и было бы странно ожидать другого от фирмы Borland, которая бдительно следит за обратной совместимостью приложений. Посмотрите к примеру на страницу Win 3.1 Палитры компонентов — новые поколения программистов никогда не видели "прабабушку" Windows XP, и все же исторические компоненты занимают свое исконное место!
Во-вторых, схема создания и внедрения отчетов в приложения Delphi практически не изменилась. В Rave Reports имеются и глобальный класс отчета, и классы полос, и компоненты преобразования данных. Существенным нововведением можно считать только визуальную среду создания отчетов, что несомненно облегчит жизнь создателей отчетов и сделает их работу эффективнее и приятнее.
Тем не менее, в Delphi 7 генератор отчетов Rave Reports является основным средством создания отчетов и его компоненты устанавливаются в Палитре компонентов по умолчанию на странице Rave. Поэтому главы этой части посвящены разнообразным аспектам разработки отчетов в Rave Reports.
В данной главе рассматриваются следующие вопросы:
какие компоненты входят в состав Rave Reports и на какие функциональные группы они делятся; что такое проект отчета и его структура; как включить отчет в состав приложения и какие компоненты для этого необходимы; компоненты управления отчетами.Отчет в приложении Delphi
Завершив обзор нового генератора отчетов, давайте обратимся к деталям программирования и посмотрим, что нужно сделать, чтобы приложение могло работать с отчетами.
Основой отчета является файл отчета с расширением rav. Он создается в визуальной среде разработки Rave Reports и может содержать произвольное число страниц. Каждая страница может быть оформлена графическими или текстовыми элементами или отображать данные из какой-либо базы данных. Другими словами, файл RAV — это проект будущего отчета, содержащий общую информацию об отчете, оформление его страниц и правила их заполнения.
После создания проект отчета необходимо связать с приложением Delphi. Для этого используется компонент TRvProject (рис. 23.1). Этот компонент обеспечивает представление отчета в приложении.
Но этого недостаточно, чтобы просмотреть или напечатать отчет. Для выполнения этих операций используется код ядра генератора отчета, который автоматически прикомпилируется к исполняемому коду приложения при переносе на любую форму проекта компонентов TRvProject и TRvSystem. Для управления операциями печати и просмотра в проекте должен присутствовать компонент TRvSystem (рис. 23.1).
Рис. 23.1. Компоненты Rave Reports в приложении Delphi
Для того чтобы приложение Delphi могло выполнять функции печати отчетов, разработчик должен выполнить следующий набор операций.
1. При помощи визуальной среды разработки Rave Reports необходимо создать проект отчета и сохранить его (см. гл. 24).
2. Перенести в проект приложения в Delphi компонент TRvProject и связать его с файлом проекта отчета (см. ниже) при помощи свойства ProjectFile.
3. Перенести в проект приложения в Delphi компонент TRvSystem и связать его с компонентом TRvProject. ля этого используется свойство Engine компонента TRvProject (см. ниже).
4. Написать код приложения, обеспечивающий просмотр и печать отчета (при необходимости и другие операции), используя методы компонента TRvProject (см. ниже).
Конечно же, это наиболее простой способ включения отчета в приложения. Для решения более сложных задач необходимо изучить использованные выше компоненты более детально.
В качестве основного средства создания
В качестве основного средства создания отчетов и их использования в приложениях в состав Delphi 7 включен генератор отчетов Rave Reports 5.0. В его состав входят ядро генератора отчетов, визуальная среда создания отчетов и набор компонентов.
Ядро генератора отчетов обеспечивает предварительный просмотр или печать отчета. Оно включается в исполняемый файл приложения. Поэтому разработчики избавлены от необходимости распространять совместно с приложением какие-либо дополнительные файлы.
Визуальная среда создания отчетов позволяет разрабатывать самые разнообразные отчеты, в том числе использующие наборы данных из источников различных типов.
Набор компонентов предоставляет разработчику инструментарий для управления отчетом в приложении.
Диалог настройки печати компонента
. Диалог настройки печати компонента TRvSystem Его заголовок определяется свойством property TitleSetup: TFormatString;
Перед открытием этого окна вызывается метод-обработчик
property OnPreviewSetup: TNotifyEvent;
Кроме этого, для диалога настройки печати можно задать ряд дополнительных параметров. Это делается в свойстве
type
TSystemSetup = (ssAllowSetup, ssAllowCopies, ssAllowCollate,
ssAllowDuplex, ssAllowDestPreview, ssAllowDestPrinter, ssAllowDestFile,
ssAllowPrinterSetup);
TSystemSetups = set of TSystemSetup;
property SystemSetups: TSystemSetups;
Элементы множества TSystemSetup означают следующее:
ssAllowSetup — разрешает или запрещает использование диалога настройки печати компонента; ssAllowCopies — управляет доступностью установки числа копий отчета; ssAllowCollate — разрешает или запрещает настройку режима печати с разбором страниц по копиям; ssAllowDuplex — разрешает или запрещает настройку двусторонней печати; ssAllowDestPreview — разрешает или запрещает использование окна предварительного просмотра; ssAiiowDestPrinter — разрешает или запрещает использование принтера; ssAllowDestFile — разрешает или запрещает использование файла для вывода отчета; ssAiiowPrinterSetup — разрешает или запрещает использование диалога настройки параметров принтера. Во время выполнения любой из перечисленных операций вывода отчета открывается окно состояния процесса (рис. 23.5). Его заголовок определяется свойством
property TitleStatus: TFormatString;
Рис. 23.5. Форма состояния процесса вывода отчета компонента TRvSystem
В нем отображается информационная строка состояния, которая может быть настроена при помощи свойств объекта SystemFiler, представленного в компоненте TRvSystem одноименным свойством.
Вложенное свойство
property StatusFormat: string;
определяет строку форматирования для текста о состоянии процесса. Для нее предусмотрены следующие управляющие символы:
%с — текущее состояние процесса вывода;
%р — номер текущей страницы; %f — номер первой страницы; %1 — номер последней страницы; %d — название устройства вывода ( название принтера, имя файла, предварительный просмотр); %r — имя драйвера устройства вывода; %s — общее число страниц; %t — порт печати; %о-%9 — номера строк для свойства statusText (см. ниже). Вложенное свойство
property StatusText: TStrings;
позволяет задать до десяти строк (можно задать и больше, но они не будут восприняты строкой статуса) с какой-либо дополнительной информацией, описывающей процесс вывода. Первая строка списка будет выведена при наличии в свойстве statusFormat управляющего символа %0, вторая — при наличии символа %1 и т. д.
При помощи перечисленных свойств вы сможете детально описать процесс вывода отчета. В этом вам помогут методы-обработчики событий компонента TRvSystem.
До начата печати отчета и по его окончании (даже если печать была прервана) соответственно вызывается пара методов-обработчиков:
property OnBeforePrint: TNotifyEvent;
property OnAfterPrint: TNotifyEvent;
В начале печати непосредственно отчета (не заголовка) вызывается метод-обработчик
property OnPrint: TNotifyEvent;
Если вы печатаете одну страницу, будет вызван метод-обработчик
type
TPrintPageEvent = function(Sender: TObject;
var PageNum: Integer): Boolean;
property OnPrintPage: TPrintPageEvent;
Но до начала печати вызывается метод-обработчик
property OnNewPage: TNotifyEvent;
который обозначает генерацию страницы.
При печати колонтитулов в верхней и нижней частях страницы вызываются методы-обработчики
property OnPrintHeader: TNotifyEvent;
property OnPrintFooter: TNotifyEvent;
Разработчик может задать несколько опций для всего компонента TRvSystem, управляя тем самым процессом вывода отчета. Для это используется свойство
type
TSystemOption = (soUseFiler, soWaitForOK, soShowStatus, soAllowPrintFromPreview, soPreviewModal);
TSystemOptions = set of TSystemOption;
property SystemOptions: TSystemOptions;
Элементы типа TSystemOptions обозначают следующее:
soUseFiler — при установке этой опции в значение True вывод будет направляться в файл, заданный свойством SystemFiler, независимо от других настроек компонента; SoWaitForOK — если включить эту опцию, генерация отчета будет задержана до момента, когда пользователь нажмет кнопку ОК в диалоге настройки печати компонента (см. рис. 23.4); soshowstatus — эта опция управляет видимостью окна состояния процесса вывода отчета в компоненте; soAiiowPrintFromPreview — будучи включенной, эта опция позволяет печатать отчет из окна предварительного просмотра; soPreviewModal — при значении True делает окно предварительного просмотра модальным.
Содержание раздела