Аутентификация пользователя в отчете
Аутентификация пользователя в отчете
Два объекта Rave Reports позволяют включить в проекте отчета механизм проверки имени пользователя и пароля. Это объекты Simple Security Controller (элемент simpleSecurity) и Data Lookup Security Controller (элемент LookupSecurity), которые доступны для выбора в диалоге создания объектов доступа к данным визуальной среды Rave Reports (см. Рисунок 24.4). Создается новый объект командой File | New Data Object главного меню Rave Reports. Созданный объект появляется в ветви Data View Dictionary дерева проекта.
Элемент SimpleSecurity предназначен для хранения списка пользователей и их паролей. Он имеет свойство userList, в котором в формате userName = Password заносятся имена и пароли пользователей.
Элемент LookupSecurity обеспечивает загрузку имен пользователей и паролей из таблицы базы данных. Для этого к нему через свойство Dataview должен быть подключен соответствующий просмотр данных. В свойстве userField необходимо указать поле, которое содержит имена пользователей, а в свойстве PasswordField задать поле с паролями.
Теперь несколько слов о том, как подключить созданные объекты.
Вы можете организовать аутентификацию на двух уровнях — уровне проекта и уровне отчета. В обоих случаях используется свойство securityControl объекта проекта или отчета. В нем необходимо выбрать нужный объект аутентификации.
Однако это действие не сделает ваши отчеты сколько-нибудь защищеннее — все необходимые проверочные операции придется написать самому в исходном коде приложения Delphi. Единственное отличие в аутентификации по уровням в том, где именно вы сможете получить доступ к объекту аутентификации — из компонента проекта или отчета.
Для организации простейшей проверки имени пользователя и пароля на уровне проекта в приложении нужно написать примерно такой код:
rpReport.Open; if rpReport.ProjMan.SecurityControi.IsValidUser(
edUserName.Text, edPassword.Text}
then rpReport.Execute
else ShowMessage('Доступ запрещен');
rpReport.Close;
В данном случае доступ к объекту аутентификации SecurityControд (класс TRaveBaseSecurity) осуществляется через объект менеджера проекта projMan (класс TRaveProjectManager).
Метод
function IsValidUser(AUserName: string;
APassword: string): Boolean;
этого объекта возвращает значение True, если переданные в параметрах имя и пароль не совпадают со значениями из списка или базы данных.
Для уровня отчета код выглядит так:
rpReport.Open;
if rpReport.ProjMan.ActiveReport.
SecurityControl.IsValidUser(
edUserName.Text, edPassword.Text)
then rpReport.Execute
else ShowMessage('Доступ запрещен');
rpReport.Close;
Здесь объект ActiveReport (класс TRaveReport) представляет текущий отчет.
Диалог выбора драйвера соединения
Рисунок 26.2. Диалог выбора драйвера соединения
![](image/gl-26-2.gif)
В диалоге выбора объекта доступа к данным (см. Рисунок 24.4) используются два объекта просмотра. Но с соединением на основе драйвера Rave Reports умеет взаимодействовать только один из них — Driver Data View. Для него необходимо выбрать соединение из созданных ранее в этом проекте (Рисунок 26.3).
Компоненты Rave Reports и отчеты в приложении Delphi
ГЛАВА 26
Отчеты для приложений баз данных
Генератор отчетов Rave Reports позволяет создавать отчеты, отображающие данные из различных источников данных. Для этого используются средства визуальной среды и компоненты со страницы Rave Палитры компонентов Delphi.
Приложение может воспользоваться соединением, предоставленным генератором отчетов, созданным и настроенным в проекте отчета, а также может использовать компоненты Rave Reports, которые обеспечивают передачу данных в отчет.
При помощи компонентов и визуальной среды генератора отчетов Rave Reports приложение баз данных может использовать базы данных, обращаясь к ним при помощи трех технологий доступа к данным (подробнее о технологиях доступа к данным см. часть IV), доступных по умолчанию.
ADO; db Express; BDE.Кроме этого, компонент TRvDataSetConnection (без участия визуальной среды) позволяет использовать любые наборы данных, открываемые через компоненты доступа к данным Delphi. Любой потомок компонента TDataSet может быть использован в отчете Rave Reports. Это означает, что дополнительно к перечисленным технологиям отчеты Rave Reports могут использовать компоненты InterBase Express (см. гл. 18), а также подключать клиентские наборы данных в распределенных приложениях (см. часть IV).
Еще один интересный компонент TRvCustomConnection обеспечивает доступ к любым данным не из баз данных. Для него источником данных могут быть текстовые файлы, электронные таблицы, электронная почта и т. д.
Что касается элементов оформления отчетов средствами визуальной среды Rave Reports, можно создавать отчеты самых различных типов. Естественно,
это простые табличные отчеты, а также отчеты, представляющие данные в отношении "один-ко-многим", отчеты с группировкой данных, отчеты с вычисляемыми значениями.
В этой главе рассматриваются следующие вопросы:
как подключить источник данных к отчету; типы соединений с источниками данных, используемые в проекте отчета и приложении Delphi; компоненты соединений с базами данных; использование компонента Trvcustomconnection; типы отчетов; использование вычисляемых значений.Группирующий отчет
Группирующий отчет
Отчеты, работающие с базами данных, часто должны отображать данные с различными уровнями группировки. Обычно группировка осуществляется в наборе данных, если он создается на основе запроса SQL с применением оператора GROUP BY. Но в самом наборе данных невозможно предусмотреть оформление групп записей, однако это можно сделать в отчете.
Для полосы данных, которая отображает данные из просмотра с группировкой, можно создать полосы группового заголовка и группового окончания. Для этого используются свойства GroupDataView и GroupKey. Первое должно указывать на объект группирующего просмотра, а второе задает поле или несколько полей, по которым осуществляется группировка. Применительно к оформлению отчета это означает, что при изменении значения группового ключа будут напечатаны полосы группового заголовка и окончания.
В качестве таких полос могут использоваться обычные полосы и полосы данных. Обычные полосы применяются, если группировка имеет один уровень вложенности (для каждого значения группового ключа существует одна или несколько сгруппированных записей). Полосы данных используются, если группировка имеет несколько уровней (внутри группы выделяется еще один групповой ключ и каждая запись в группе имеет еще несколько сгруппированных записей второго уровня).
Кроме этого, для полос группового заголовка необходимо в свойстве ControllerBand задать основную полосу данных и настроить свойство Bandstyle. Для группового заголовка в редакторе Band Style Editor в группе Print Location устанавливается флажок Group Header (G), а для полосы группового окончания — флажок Group Footer (g).
Обычно группирующие запросы SQL используют агрегатные функции для вычисления одного или нескольких величин по всей группе. Чаще всего это общая денежная сумма или общее количество. Такие величины удобно размещать в полосах группового окончания.
Использование вычисляемых значений
Использование вычисляемых значений
На странице Reports Палитры инструментов визуальной среды Rave Reports доступны несколько компонентов, которые позволяют применять агрегатные функции к значениям полей набора данных, переданного через соединение в отчет.
К агрегатным относятся следующие функции:
AVG — вычисление среднего; COUNT — подсчет числа элементов множества; МАХ — нахождение максимального значения; MIN — нахождение минимального значения; SUM — суммирование.Простые элементы оформления позволяют получать вычисляемые значения на базе полей одного источника данных, более сложные "умеют" объединять несколько источников данных.
Рассмотрим все эти элементы оформления.
Компонент TRvCustomConnection
Компонент TRvCustomConnection
Компонент TRvCustomConnection обеспечивает доступ к самым разнообразным источникам данных. Фактически через этот компонент разработчик может передать в отчет все данные, какие только сможет загрузить в приложение. Причина столь удивительной универсальности кроется в том, что:
во-первых, компонент изначально не ориентирован ни на один конкретный вид данных; во-вторых, работа по созданию строк отчета возлагается на разработчика, который должен использовать для этого методы-обработчики событий.Повторим банальную истину, что чудес не бывает, и, как видите, за гибкость приходится расплачиваться дополнительным объемом работы.
Для того чтобы настроить соединение через компонент TRvCustomConnection, необходимо выполнить следующие действия.
1. Определить число строк отчета и установить его в компоненте.
2. Создать структуру данных отчета (метаданные). Здесь нужно решить, какие именно поля будут присутствовать в отчете, в каком порядке, дать им названия и определить их тип данных.
3. Создать процедуру, обеспечивающую передачу данных из источника данных в текущую строку отчета.
4. Связать компонент соединения с объектом прямого просмотра.
Обсудим эту последовательность действий более детально на простом примере. Создадим небольшое приложение, которое позволяет загружать текстовые файлы в два компонента TMemo. Перенесем на форму и настроим все необходимые компоненты Rave Reports.
Затем разработаем отчет, который печатает данные из этих двух компонентов в двух колонках. Отчет тоже несложен и состоит из полос заголовка и окончания, а также полосы данных с расположенными на ней двумя элементами Оформления DataText.
Наша задача сейчас — настроить компонент TRvCustomConnection так, чтобы он мог отображать данные из двух компонентовTMemo.
Листинг 26.1. Методы -обработчики событий компонента TRvCustomConnection, обеспечивающего соединение отчета с массивами Memo
procedure TfmMain.rcCustomOpen(Connection: TRvCustomConnection);
begin
Connection.DataRows := Max(meLeft.Lines.Count, meRight.Lines.Count);
i := 0;
end;
procedure TfmMain.rcCustomGetCols(Connection: TRvCustomConnection);
begin
Connection.WriteField('LeftColumn', dtString, 40, 'LeftColumn', '');
Connection.WriteField('RightColumn1, dtString, 40, 'RightColumn1, '');
end;
procedure TfmMain.rcCustomGetRow(Connection: TRvCustomConnection);
begin
if meLeft.Lines.Count >= i
then Connection.WriteStrData('', meLeft.Lines[i])
else Connection. WriteNullData; if meRight.Lines.Count >= i
then Connection.WriteStrData('', meRight.Lines[i])
else Connection. WriteNullData;
Inc(i);
end;
При открытии соединения в методе-обработчике onopen рассчитывается число записей, необходимое для отображения наиболее длинного из двух файлов.
Метод-обработчик onGetCols вызывается, когда отчету необходимы метаданные о наборе данных соединения. Здесь создаются два поля.
Для этого используется метод
procedure WriteField(Name: String; DataType; TRPDataType; Width: Integer; FullName: String; Description: String);
который создает поле в соответствии с переданными в нем параметрами.
И при печати отчета для каждой строки вызывается метод-обработчик OnGetRow, в котором задаются значения полей. Для каждого типа данных используется свой метод:
function WriteBCDData(FormatData: String; NativeData: Currency): String;
function WriteBlobData(var: Buffer; Len: Longint): String;
function WriteBoolData(FonnatCata: String; NativeData: Boolean): String;
function WriteCurrData(FormatData: String; NativeData: Currency): String;
function WriteDateTime(FormatData: String; NativeData: TDateTime);
function WriteFloatData(FomatData: String; NativeData: Extended): String;
function WritelntData(FormatData: String; NativeData: Integer): String;
function WriteNullData;
function WriteStrData(FormatData: String; NativeData: String): String;
Обратите внимание, что все эти методы не определяют, какому именно полю будет присвоено значение. Поэтому присваивание осуществляется в порядке следования полей: первый по порядку метод отправляет в отчет значение для первого поля, второй для второго и т. д.
Примечание
Примечание
Методы-обработчики компонентов TRvCustomConnection и TRvDataSetConnection совпадают (см. выше разд. "Компонент TRvDataSetConnection" данной главы).
Теперь осталось связать соединение с проектом отчетов. Это делается стандартным образом — при создании объекта прямого просмотра. Но здесь есть одна особенность. Как уже говорилось выше, при создании прямого просмотра в нем автоматически создаются объекты полей, соответствующие полям набора данных. И теперь мы знаем, что у компонента соединения имеется специальный метод-обработчик OnGetCols, который вызывается при создании полей.
Однако, если вы создадите объект прямого просмотра обычным способом, визуальная среда Rave Reports создаст один-единственный объект поля, не имеющего ничего общего с реальными метаданными. Для того чтобы поля импортировались в проект отчета правильно, необходимо, чтобы при создании объекта просмотра приложение, содержащее компонент TRvDataSetConnection, было запущено. Тогда в диалоге выбора соединений (см. Рисунок 26.4) необходимо включить флажок Runtime, и вы увидите компонент нужного соединения. В этом случае объект прямого просмотра получит все необходимые поля, которые затем следует связать с элементами оформления отчета.
Компонент TRvDataSetConnection
Компонент TRvDataSetConnection
Компонент TRvDataSetConnection позволяет отчету получить доступ к наборам данных, инкапсулированных в любых компонентах, произошедших от класса TDataSet. Это открывает перед разработчиком самые широкие возможности по созданию отчетов для любых приложений баз данных и распределенных приложений.
Сразу после переноса на форму компонент становится доступным в визуальной среде Rave Reports при создании объекта прямого просмотра. Однако толк от ненастроенного соединения пока небольшой. Сначала его нужно связать с компонентом набора данных. Для этого предназначено свойство
property DataSet: TDataSet;
И это все. Теперь созданный в визуальной среде объект прямого просмотра автоматически получит объекты полей, соответствующие полям в наборе данных компонента DataSet.
Впрочем, еще несколько вспомогательных свойств могут дать разработчику дополнительные удобства.
Примечание
Примечание
Здесь мы рассмотрим только часть свойств и методов. Компонент TRvDataSetConnection обладает большой группой свойств и методов, которые, будучи использованы в методах-обработчиках событий, позволяют дополнительно оформлять отчет. Более детально эти свойства и методы рассматриваются ниже в разд. "Компонент TRvCustomConnection" данной главы.
Свойство
property FieldAliasList: TStrings;
пригодится, если нужно изменить имена полей в прямом просмотре проекта отчета. Для этого в списке свойства в формате Name = Alias задаются имена полей связанного набора данных и их псевдонимы, которые будут использованы в объекте прямого просмотра.
Методы-обработчики событий компонента отслеживают процесс навигации по набору данных при печати отчета.
При открытии соединения для создания отчета генератором отчетов вызывается метод-обработчик
type
TRPConnectorEvent = procedure(Connection: TRvCustomConnection);
property OnOpen: TRPConnectorEvent;
При открытии соединения отчет требует передать ему информацию о структуре набора данных (метаданные). Компонент соединения делает это и вызывает метод-обработчик
property OnGetCols: TRPConr.ectorEvent;
Когда курсор устанавливается на первую строку набора данных, вызывается метод-обработчик
property OnFirst: TRPConnectcrEvent;
а при перемещении на следующую запись можно использовать метод
property OnNext: TRPConnectorEvent;
Если генератор отчетов нашел нужную запись и считал ее для представления в отчете, для отслеживания этого события разработчик может использовать метод
property OnGetRow: TRPConnectorEvent;
При достижении последней записи набора вызывается метод-обработчик
type
TRPEOFEvent = procedure(Connection: TRvCustomConnection; var Eof: Boolean);
property OnEOF: TRPEOFEvent;
При повторном использовании набора данных, например при печати отчетов "один-ко-многим" или при группировке записей, соединение обновляется. При этом все параметры соединения и набора данных приводятся к исходному состоянию и вызывается метод-обработчик
property OnRestore: TRPConnectorEvent;
Кроме этого, при печати сложных отчетов генератору отчетов может понадобиться некоторое подмножество отсортированных записей набора данных.
Перед началом сортировки и фильтрации и после их завершения вызываются пары методов-обработчиков
property OnGetSort: TRPConnectorEvent;
property OnSetSort: TRPConnectorEvent;
И
property OnGetFilter: TRPConnectorEvent;
property OnSetFilter: TRPConnectorEvent;
Однако вы можете обеспечить дополнительную фильтрацию записей, передаваемых из набора данных в отчет. Для этого используется метод-обработчик
type
TRPValidateRowEvent = procedure(Connection: TRvCustomConnection;
var ValidRow: Boolean);
property OnValidateRow: TRPValidateRowEvent;
Параметр ValidRow управляет отправкой отдельной записи отчету: при значении True запись пропускается в отчет.
Рассмотрим простой пример. Для отчета, печатающего всем нам хорошо известную таблицу COUNTRY из демонстрационной базы Delphi, можно ограничить список стран, а также исключить страны с площадью территории, меньше заданной:
procedure TForml.RvSomeConValidateRow(Connection: TRvCustomConnection;
var ValidRow: Boolean);
begin
with TRvDataSetConnection(Connection) do
ValidRow :=DataSet.FieldByName('Area').AsInteger > 1000000;
end;
Компоненты, использующие BDE
Компоненты, использующие BDE
В состав набора компонентов соединений Rave Reports включены два компонента, которые обеспечивают связь прямого просмотра в проекте отчета с набором данных BDE.
Компонент TRvTableConnection работает с компонентом TTаblе. Для связывания с таблицей BDE используется свойство
property Table: TTable;
Еще одно свойство
property UseSetRange: Boolean;
при значении True определяет, что при создании отчета будут использованы механизмы фильтрации и сортировки компонента TTаblе.
Компонент TRvQueryConnection работает с компонентом TQuery.
Свойство
property Query: TQuery;
задает компонент TQuery.
Остальные свойства и методы этих компонентов соответствуют компоненту TRvDataSetConnection.
Отчет "один-ко-многим"
Отчет "один-ко-многим"
При помощи средств Rave Reports можно создавать и более сложные отчеты. В приложениях баз данных очень часто используются отношения "один-ко-многим" между наборами данных.
Давайте посмотрим, как создать отчет "один-ко-многим". Само собой, он должен быть связан как минимум с двумя просмотрами, которые находятся в отношении "один-ко-многим".
Для компонентов наборов данных в Delphi не нужно создавать отношение "один-ко-многим"— речь идет о том, что их поля позволяют такое отношение создать теоретически.
Подобно рассмотренному выше простому отчету, отчет "один-ко-многим" может содержать полосы Band и DataBand. Причем дополнительные настройки необходимы для обоих типов полос. Число полос DataBand должно соответствовать числу используемых в отчете наборов данных. Полосы Band несут в основном оформительскую нагрузку, и их число зависит от эскиза отчета и вашей фантазии.
В качестве примера создадим отчет для двух таблиц из демонстрационной базы данных Delphi. Таблицы CUSTOMER и ORDERS находятся в отношении "один-ко-многим". Для них в тестовом приложении создано соединение с использованием ADO, и два табличных компонента ADO подключены r компонентам соединения TRvDataSetConnection.
Соответственно полоса данных CustBand будет отображать записи из набора данных tCustomer, а полоса ordBand — из набора данных torders (Рисунок 26.6). Их необходимо связать с объектами прямых просмотров, как уже описывалось выше для примера простого отчета.
вычислительной цепочки на основе элементов CalcOp
Рисунок 26.9. Пример вычислительной цепочки на основе элементов CalcOp
![](image/gl-26-9.gif)
Это могут быть как простые элементы CalcText и calcTotal, так и другие элементы calcOp, которые, в свою очередь, могут содержать сколь угодно сложные вычислительные цепи.
Пример использования элемента CalcOp имеется в демонстрационном приложении DemoReports на дискете, прилагаемой к этой книге.
Простой табличный отчет
Простой табличный отчет
Для создания отчетов, использующих данные из источников, предоставленных объектами соединений и просмотров, используются элементы оформления со страницы Report Палитры инструментов Rave Reports.
Основой, без которой нельзя использовать полосы (элементы Band и DataBand), является элемент Region. Он ограничивает часть страницы, на которой будут печататься данные.
Главную роль в отчетах для приложений баз данных играют полосы. Это невизуальные элементы оформления, моделирующие горизонтальную область или строку отчета. На странице Report доступны два таких элемента. Обычная полоса Band создает горизонтальную область, которая не изменяет свое абсолютное или относительное положение на странице. Например, созданная на основе элемента Band полоса TitleBand всегда располагается в начале первой страницы отчета и оформляет заголовок таблицы (Рисунок 26.5). Полоса FooterBand будет напечатана сразу после основной таблицы — но ее конкретное положение на странице зависит от размера набора данных.
Редактор полос отчета Band Style Editor для отчета MasterDetailReport
Рисунок 26.7. Редактор полос отчета Band Style Editor для отчета MasterDetailReport
![](image/gl-26-7.gif)
На этом настройка отношения "один-ко-многим" завершена. Однако скажем еще несколько слов об использовании обычных полос при оформлении такого рода отчетов. В нашем примере две дополнительные полосы OrdHeaderBand и OrdFooterBand помогают визуально выделить группы записей подчиненной полосы. Для этого необходимо в их свойстве controiierBand выбрать полосу данных OrdBand. Затем в редакторе полос отчета в группе Print Location для полосы OrdHeaderBand необходимо выбрать флажок Body Header (В), а для полосы OrdFooterBand — флажок Body Footer (b).
Обратите внимание (см. Рисунок 26.6 и 26.7), что значки маркировки на полосах страницы и в редакторе полос наглядно демонстрируют текущий статус полосы. Цветом выделены уровни вложения данных и подчиненность полос. Полосы с маркировкой одного цвета печатаются в одном блоке. Квадраты обозначают полосы данных, а треугольники — обычные полосы, при этом направление вершины треугольника обозначает полосу заголовка или окончания. Левая панель редактора полос отчета Band Style Editor (Рисунок 26.7) наглядно демонстрирует модель отчета, как если бы он был напечатан для трех записей набора данных.
На основе отчета "один-ко-многим" можно легко разработать и более сложные отчеты. Для этого необходимо детальную полосу данных связать с новыми детальными полосами и настроить по описанной методике отношение "один-ко-многим".
Редактор свойства DataField
Рисунок 26.8. Редактор свойства DataField
![](image/gl-26-8.gif)
Затем параметр или переменная может быть использована для дальнейших вычислений или напечатана при помощи элемента DataText. В редакторе свойства DataField этого элемента (Рисунок 26.8) параметры и переменные отчета можно выбрать из списков Project Parameters и Post Initialize Variables.
Генератор отчетов Rave Reports позволяет
Резюме
Генератор отчетов Rave Reports позволяет создавать разнообразные отчеты для приложений баз данных.
Совокупность компонентов Delphi и средств визуальной среды Rave Reports обеспечивают создание соединений с источниками данных любых видов, в том числе и не основанных на СУБД. Соединения могут использовать технологии доступа к данным, реализованные соответствующими компонентами Delphi (см. часть IV) или драйверами Rave Reports.
Основой отчетов являются элементы Region, Band и DataBand, которые обеспечивают размножение строк отчета в соответствии с записями источников данных. Набор специализированных элементов оформления позволяет отображать в отчетах все основные типы данных.
Соединение через драйвер Rave Reports
Соединение через драйвер Rave Reports
Проект, отчеты которого используют соединение через драйверы Rave Reports, должен содержать два объекта доступа к данным (в дереве проекта они отображаются в ветви Data View Dictionary).
В первую очередь нужно создать объект соединения (компонент Database). Для этого используется команда File | New Data Object главного меню. В появившемся диалоге Data Connections (см. Рисунок 24.4) необходимо выбрать Database Connection и нажать кнопку Next — появится список Database Connection Type (Рисунок 26.2). В нем представлены все доступные в визуальной среде типы соединений. Элементы списка соответствуют файлам RDV драйверов соединений. В стандартную поставку входят драйверы для соединений ADO, BDE, dbExpress. Эти файлы по умолчанию устанавливаются в папке \Delphi7\Rave5\DataLinks.
После нажатия кнопки Finish появляется специализированный диалог настройки параметров соединения, типовой для выбранной технологии доступа к данным (см. часть IV), и создается новый объект соединения. В его свойствах AuthDesign и AuthRun содержатся настройки соединения, которые используются при его открытии на этапах разработки и выполнения отчета соответственно.
Теперь, когда соединение готово, необходимо создать просмотр данных. Он формирует набор данных, который будет передан в отчет и там связан с элементами оформления (см. Рисунок 26.1).
Соединение через компонент приложения Delphi
Соединение через компонент приложения Delphi
Второй тип соединения базируется на компоненте набора данных, который уже существует и соединен с источником данных в приложении Delphi. Основой такого соединения в приложении является один из специализированных компонентов со страницы Rave Палитры компонентов Delphi — например, компонент TRvDataSetConnection. В проекте отчета в визуальной среде Rave Reports соединение обеспечивает объект просмотра Direct Data View. При создании объекта прямого просмотра для него необходимо выбрать из списка одно из соединений из приложения Delphi, в котором будет использоваться проект отчета. В этом списке можно выбрать все соединения, доступные как во время разработки, так и во время выполнения приложения. Для этого используются флажки в нижней части окна (Рисунок 26.4).
Соединения с источниками данных в отчете Rave Reports
Рисунок 26.1. Соединения с источниками данных в отчете Rave Reports
![](image/gl-26-1.gif)
При соединении через компоненты в Delphi сначала необходимо создать объект просмотра Direct Data View, который реализует прямой доступ к набору данных на основе активного соединения в приложении Delphi. При этом соединение может быть создано на основе любой доступной в Delphi технологии доступа к данным. Это позволяет сделать набор компонентов Rave Reports на странице Rave Палитры компонентов Delphi. Это следующие технологии доступа к данным:
ADO; DbExpress; BDE; InterBase Express; клиентские наборы данных распределенных приложений; источники данных, не использующие базы данных (текстовые файлы, электронная почта, электронные таблицы и т. д.).За визуализацию данных в отчете отвечают специализированные элементы оформления, расположенные на странице Report Палитры инструментов визуальной среды Rave Reports. Они связываются с полями объекта просмотра данных, созданного разработчиком. Это может быть просмотр на основе запроса SQL, созданного разработчиком, или прямой просмотр набора данных Delphi. Просмотр объединяет нужные поля из таблиц, которые доступны через соединение с источником данных (см. гл. 24). Для этого используется свойство Dataview элементов оформления.
Соединения с источниками данных в приложении
Соединения с источниками данных в приложении
Теперь давайте посмотрим, как нужно использовать специализированные компоненты Rave Reports в приложениях Delphi для того, чтобы создать соединение отчета с источником данных. Их основная задача — передать в отчет связанный набор данных.
Для создания соединения можно использовать следующие компоненты:
TRvcustomConnection — обеспечивает доступ к источникам данных, не основанным на базах данных; TRvDataSetConnection — создает соединение с любыми компонентами наборов данных, предком которых является класс TDataSet; TRvTabieConnection — создает соединение с компонентом TTаblе; TRvQueryConnection —создает соединение с компонентом TQuery.После переноса на форму проекта Delphi эти компоненты становятся доступны для выбора при создании прямых просмотров Direct Data View в визуальной среде создания отчетов Rave Reports. Однако перед этим компонент соединения необходимо связать с набором данных.
Соединения с источниками данных в Rave Reports
Соединения с источниками данных в Rave Reports
Если отчет Rave Reports должен отображать данные из какого-либо источника данных, на этапе разработки в визуальной среде в проект отчета должны быть добавлены специальные объекты, обеспечивающие соединение с источником данных и формирование набора данных, который затем отображается в отчете.
В Rave Reports существуют два типа соединений с источниками данных (Рисунок 26.1):
соединение через драйвер Rave Reports; соединение через компонент Rave Reports и компонент набора данных в приложении Delphi.В обоих случаях соединение инкапсулировано в объекте визуальной среды, а различаются только способы доступа к данным и набор компонентов, необходимый для этого.
При соединении через драйвер Rave Reports проект отчета на этапе разработки и ядро генератора отчетов на этапе выполнения используют драйверы, которые реализованы в виде файлов с расширением rvd. Именно наличие этих файлов предопределяет выбор технологий доступа к данным при создании объекта соединения в среде разработки (Рисунок 26.2). В стандартную поставку Rave Reports 5.0 входят драйверы для следующих технологий доступа к данным:
ADO; dbExpress; BDE.
Соединения с источниками данных в визуальной среде Rave Reports
Соединения с источниками данных в визуальной среде Rave Reports
Любой отчет, работающий с базами данных, должен быть настроен соответствующим образом в визуальной среде создания отчетов Rave Reports. Независимо от типа соединения здесь должен быть создан хотя бы один объект доступа к данным.
Если вы хотите создать соединение через драйверы Rave Reports, вам потребуется объект соединения и связанный с ним объект просмотра на основе запроса SQL.
При необходимости использовать соединение на основе компонентов Delphi в визуальной среде вам потребуется создать объект прямого просмотра.
Рассмотрим подробнее действия, которые необходимо выполнить в визуальной среде Rave Reports для создания и настройки соединения.
Список доступных соединений из приложения Delphi
Рисунок 26.4. Список доступных соединений из приложения Delphi
![](image/gl-26-4.gif)
После создания объект прямого просмотра (компонент DataView) доступен в дереве проекта и может быть использован. В его состав входят все поля набора данных, связанного в приложении Delphi с компонентом соединения. И их можно подключить к элементам оформления нужного отчета (см. разд. "Типы отчетов" ниже в данной главе).
Список выбора соединения для просмотра Driver Data View
Рисунок 26.3. Список выбора соединения для просмотра Driver Data View
![](image/gl-26-3.gif)
После этого в редакторе Query Advanced Designer (см. Рисунок 24.5) создается запрос SQL, используемый для создания набора данных просмотра. Подробнее о пользовательском интерфейсе этого редактора рассказывается в гл. 24.
После создания объекта просмотра (компонент DriverDataview) в его свойстве Database появляется ссылка на объект выбранного соединения, а свойство Query позволяет редактировать запрос SQL просмотра. Для этого используется все тот же редактор Query Advanced Designer. Соединение просмотра можно изменить, выбрав новое в списке свойства Database, однако при этом может потребоваться частично или полностью переписать запрос SQL.
В состав объекта просмотра входят объекты всех полей, определенных на этапе разработки для запроса SQL.
Теперь подготовленный в просмотре набор данных можно подключить к элементам оформления нужного отчета (см. разд. "Типы отчетов" ниже в данной главе).
Страница отчета MasterDetailReport в визуальной среде Rave Reports
Рисунок 26.6. Страница отчета MasterDetailReport в визуальной среде Rave Reports
![](image/gl-26-6.gif)
А теперь займемся созданием отношения "один-ко-многим".
В подчиненной полосе данных ordBand необходимо задать значения для четырех свойств.
Свойство controllerBand должно содержать ссылку на главную полосу CustBand. Свойство MasterDataview должно содержать ссылку на главный объект просмотра CustomersView. В свойстве MasterKey необходимо задать ключевое поле custNo главного просмотра CustomersView, по которому будет установлено отношение. В свойстве DetailKey необходимо задать ключевое поле CustNo подчиненного просмотра Ordersview, по которому будет установлено отношение.Кроме этого, необходимо настроить атрибуты местоположения полос на странице отчета. Для этого используется Редактор полос отчета Band Style Editor (Рисунок 26.7), который открывается при щелчке на кнопке свойства Bandstyle Инспектора объектов визуальной среды Rave Reports. В нем в группе Print Location для подчиненной полосы ordBand необходимо установить флажок Detail.
Страница простого табличного отчета в визуальной среде Rave Reports
Рисунок 26.5. Страница простого табличного отчета в визуальной среде Rave Reports
![](image/gl-26-5.gif)
Элемент оформления DataBand обеспечивает размножение строк отчета в соответствии с числом строк набора данных. Для этого полосу данных MainDataview необходимо связать с просмотром при помощи свойства Dataview. В нашем примере это прямой просмотр countryview, связанный с компонентом соединения TRvDataSetConnection в приложении.
На полосе данных MainDataview необходимо разместить элементы оформления DataText. Каждый из этих элементов связывается с объектом просмотра и полем данных такого просмотра. Для этого используются свойства Dataview и DataField соответственно. Таким образом, каждый элемент оформления DataText размножается вместе с полосой данных и формирует в отчете колонку значений поля набора данных.
Расположив на полосах горизонтальные и вертикальные линии, можно легко оформить данные в табличном виде.
Свойства элемента СаlсОр для определения двух источников данных
Таблица 26.1. Свойства элемента СаlсОр для определения двух источников данных
Первый источник |
Второй источник |
Назначение свойства |
SrclCalcVar |
Src2CalcVar |
Определяет вычисляемый элемент, результат которого берется в качестве исходного |
SrclDataField |
Src2DataField |
Задает поле просмотра, над значениями которого проводятся вычисления. Игнорируется при заданном свойстве SrcXCalcVar |
SrclDataView |
Src2DataView |
Задает поле просмотра, над значениями которого проводятся вычисления. Игнорируется при заданном свойстве SrcXCalcVar |
SrclFunction |
Src2Function |
Позволяет выбрать математическую функцию (их список гораздо шире, чем просто агрегатные функции), которая будет выполнена над исходным значением перед вычислением основной операции элемента |
SrclValue |
Src2Value |
Задает фиксированное значение, над которым производится вычислительная операция |
Собственно функция, которая должна обработать значения из двух заданных источников, задается свойством operator.
После выполнения основной операции результат может быть обработан еще один раз, если вы зададите математическую функцию в свойстве
ResultFunction.
Таким образом, при помощи элемента CalcOp разработчик может реализовывать довольно сложные вычисления.
Если задать в качестве двух источников данных:
два фиксированных значения (свойства srcivalue и Src2value); два поля из одного или двух просмотров данных (свойства SrciDataField и Src2DataField); комбинацию первых двух вариантовто их значения будут последовательно обработаны вычислительной операцией, которую вы зададите свойствами:
SrclFunction; SrclFunction; Operator; ResultFunction.Кроме этого, элемент CalcOp позволяет создавать вычислительные цепочки, если использовать в качестве одного или двух источников другие вычислительные элементы (Рисунок 26.9).
Типы отчетов
Типы отчетов
Сейчас мы займемся вопросами разработки собственно отчетов. Схема использования элементов оформления, работающих с объектами доступа к данным, стандартна для любых типов отчетов. Поэтому сначала мы рассмотрим общую методику на примере простого отчета, а затем перейдем к более сложным отчетам.
Для всех рассматриваемых типов отчетов создано демонстрационное приложение DemoReports.
Управляющие вычислительные элементы
Управляющие вычислительные элементы
Выше мы упоминали о свойстве Controller элементов CalcText и calcTotal, которое позволяет определить момент начала вычислений. Для этого используется специальный невизуальный элемент CalcController. Обычно он располагается на той же полосе, что и вычислительные элементы и инициализирует процесс вычисления в момент своей печати. Хотя на самом деле невизуальный элемент CalcController не печатается, тем не менее событие onBeforePrint он получает исправно вместе со всеми элементами, расположенными на данной полосе. А значит и с инициализацией вычислений он справится вполне.
Обладая несколькими специфическими свойствами, он позволяет определить момент начала вычислений более точно. И так же, как элемент FontMaster используется для централизованного управления шрифтами, этот элемент может быть центром управления вычислениями.
Свойство initcaicvar должно ссылаться на другой вычислительный элемент. И вычисленное им значение будет использовано в качестве начального.
Свойство initDataField задает поле данных, значение которого используется в качестве начального. Работает, если свойство initcaicvar не задано.
Свойство initvalue задает начальное значение, если предыдущие два свойства не заданы.
Для того чтобы эти свойства работали и задавали начальное значение, ссылка на элемент CalcController должна присутствовать в свойстве Initializer элементов оформления Cal или CalcTotal.
Элемент DataCycle используется для дополнительной фильтрации, сортировки и просмотра данных, поля которого используются для вычислений. С его помощью можно получить нужное для вычислений подмножество записей набора данных, не изменяя просмотра данных.
Свойство oataview задает просмотр данных, с которым будет работать элемент DataCycle.
При помощи свойств MasterDataView, MasterKey и DetailKey можно получить подмножество записей для отношения "один-ко-многим".
Свойство sortKey позволяет отсортировать записи по заданному полю.
Вычисляемые значения по нескольким источникам
Вычисляемые значения по нескольким источникам
Вычислительный элемент calcOp позволяет проводить вычислительные операции над значениями из двух различных источников.
Разработчик должен задать исходные значения и источники данных, используя два набора свойств (табл. 26.1). Назначение части этих свойств вам уже знакомо (см. разд. "Вычисляемые значения по одному источнику" выше).
Вычисляемые значения по одному источнику
Вычисляемые значения по одному источнику
Для вычисления агрегатного значения одного или нескольких полей одного источника данных используются два элемента оформления.
Элемент CalcText позволяет отобразить результат вычисления на полосе отчета. Так же, как и обычный элемент DataText, его необходимо связать с просмотром и полем. Для этого используются свойства DataView и DataField соответственно.
Кроме этого, элемент CalcText должен быть связан со специализированным элементом CaicController (см. ниже), который будет управлять процессом вычисления. Связывание осуществляется при помощи свойства Controller.
В свойстве CalcType задается одна из пяти перечисленных выше агрегатных функций.
Дополнительно, для функции COUNT можно настроить еще три свойства. При необходимости включить или отключить подсчет нулевых значений или пробелов используются булевские свойства countNulls и countBianks соответственно. А свойство Countvalue позволяет задать значение поля, которое будет учитываться при расчете функции, все остальные значения будут игнорироваться.
Вычисленное значение при сохранении может быть отформатировано в соответствии с шаблоном, заданным свойством DisplayFormat.
Для всех агрегатных функций можно задать момент начала вычислений. Для этого в свойстве initializer необходимо указать элемент оформления отчета, и при его печати начнется вычисление. Это может быть любой элемент, расположенный с элементом CalcText на одной полосе. Но желательно использовать для этого специализированный элемент calccontroller (см. ниже).
Примечание
Примечание
Пример использования элемента оформления CalcText имеется в отчетах, рассмотренных нами выше.
Элемент CalcTotal является невизуальным аналогом элемента CalcText. Поэтому он обладает всеми свойствами, о которых рассказывается выше для элемента CalcText. Вычисленное при его печати значение разработчик может использовать по своему усмотрению после того, как оно сохранено. Приемником вычисленного значения может быть один из параметров объекта отчета.
Свойство DestParam позволяет выбрать один из предопределенных или созданных разработчиком параметров отчета (свойство Parameters объекта отчета).
Свойство DestPiVar задает переменную отчета, в которую будет передано вычисленное значение (свойство pivars объекта отчета).
Компоненты Rave Reports и отчеты в приложении Delphi
ГЛАВА 27
Стандартные технологии программирования
В этой главе обсуждаются вопросы использования стандартных для приложений Windows технологий программирования. С их помощью ваше приложение обретет законченный вид и будет соответствовать необходимым канонам и правилам пользовательского интерфейса.
В настоящей главе рассматриваются следующие вопросы:
интерфейс переноса Drag-and-Drop; механизм Drag-and-Dock; усовершенствованное масштабирование; как правильно передавать фокус между элементами управления; управление мышью; ярлыки.Главная форма проекта DemoDragDrop
Рисунок 27.1. Главная форма проекта DemoDragDrop
![](image/gl-27-1.gif)
Листинг 27.1. Секция implementation модуля главной формы проекта DemoDragDrop
implementation
{$R *.DFM)
procedure TMainForm.EditlMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, У: Integer);
begin if Button = mbLeft
then TEdit(Sender).BeginDrag(True);
end;
procedure TMainForm.Edit2DragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
begin
if Source is TEdit
then Accept := True
else Accept :<= False;
end;
procedure TMainForm.Edit2DragDrop(Sender, Source: TObject; X, Y:
Integer);
begin
TEdit(Sender).Text := TEdit(Source).Text;
TEdit(Sender).SetFocus;
TEdit(Sender).SelectAll;
end;
procedure TMainForm.EditlEndDrag(Sender, Target: TObject; X, Y: Integer);
begin if Assigned(Target)
then TEdit(Sender).Text := 'Текст перенесен в ' + TEdit(Target).Name;
end;
procedure TMainForm.FormDragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean); begin if Source.ClassName = 'TPanel'
then Accept := True
else Accept := False;
end;
procedure TMainForm.FormDragDrop(Sender, Source: TObject; X, Y: Integer);
begin
TPanel(Source).Left := X;
TPanel(Source).Top := Y;
end;
end.
Для однострочного редактора Edit1 определены методы-обработчики источника. В методе EditiMouseDown обрабатывается нажатие левой кнопки мыши
и включается механизм переноса. Так как свойство DragMode для Edit1 имеет значение dmManual, то компонент без проблем обеспечивает получение фокуса и редактирование текста.
Метод EditiEndDrag обеспечивает отображение информации о выполнении переноса в источнике.
Для компонента Edit2 определены методы-обработчики приемника. Метод Edit2DragOver проверяет класс источника и разрешает или запрещает прием.
Метод Edit2DragDrop осуществляет перенос текста из источника в приемник.
Примечание
Примечание
Обратите внимание, что оба компонента TEdit одновременно являются источниками и приемниками. Для этого каждый из них использует методы-обработчики другого. А исходный код методов настроен на обработку владельца как экземпляра класса TEdit.
Форма, как приемник Drag-and-Drop, обеспечивает перемещение панели Panel2, которая выступает в роли источника. Метод FormDragOver запрещает прием любых компонентов, кроме панелей. Метод FormDragDrop осуществляет перемещение компонента.
Панель не имеет своих методов-обработчиков, т. к. работает в режиме dmAutomatic и не нуждается в дополнительной обработке завершения переноса.
Интерфейс переноса Drag-and-Drop
Интерфейс переноса Drag-and-Drop
Интерфейс переноса и приема компонентов появился достаточно давно. Он обеспечивает взаимодействие двух элементов управления во время выполнения приложения. При этом могут выполняться любые необходимые операции. Несмотря на простоту реализации и давность разработки, многие программисты (особенно новички) считают этот механизм малопонятным и экзотическим. Тем не менее использование Drag-and-Drop может оказаться очень полезным и простым в реализации. Сейчас мы в этом убедимся.
Для того чтобы механизм заработал, требуется настроить соответствующим образом два элемента управления. Один должен быть источником (Source), второй — приемником (Target). При этом источник никуда не перемещается, а только регистрируется в качестве такового в механизме.
Примечание
Примечание
Один элемент управления может быть одновременно источником и приемником.
Пользователь помещает указатель мыши на нужный элемент управления, нажимает левую кнопку мыши и, не отпуская ее, начинает перемещать курсор ко второму элементу. При достижении этого элемента пользователь отпускает кнопку мыши. В этот момент выполняются предусмотренные разработчиком действия. При этом первый элемент управления является источником, а второй — приемником.
После выполнения настройки механизм включается и реагирует на перетаскивание мышью компонента-источника в приемник. Группа методов-обработчиков обеспечивает контроль всего процесса и служит для хранения исходного кода, который разработчик сочтет нужным связать с перетаскиванием. Это может быть передача текста, значений свойств (из одного редактора в другой можно передать настройки интерфейса, шрифта и сам текст); перенос файлов и изображений; простое перемещение элемента управления с места на место и т. д. Пример реализации Drag-and-Drop в Windows — возможность переноса файлов и папок между дисками и папками.
Как видите, можно придумать множество областей применения механизма Drag-and-Drop. Его универсальность объясняется тем, что это всего лишь средство связывания двух компонентов при помощи указателя мыши. А конкретное наполнение зависит только от фантазии программиста и поставленных задач.
Весь механизм Drag-and-Drop реализован в базовом классе TControl, который является предком всех элементов управления. Рассмотрим суть механизма.
Любой элемент управления из Палитры компонентов Delphi является источником в механизме Drag-and-Drop. Его поведение на начальном этапе переноса зависит от значения свойства
type TDragMode = (dmManual, dmAutomatic);
property DragMode: TDragMode;
Значение dmAutomatic обеспечивает автоматическую реакцию компонента на нажатие левой кнопки мыши и начало перетаскивания — при этом механизм включается самостоятельно.
Значение dmManual (установлено по умолчанию) требует от разработчика обеспечить включение механизма вручную. Этот режим используется в том случае, если компонент должен реагировать на нажатие левой кнопки мыши как-то иначе. Для инициализации переноса используется метод
procedure BeginDrag(Immediate: Boolean;
Threshold: Integer = -1);
Параметр immediate = True обеспечивает немедленный старт механизма. При значении False механизм включается только при перемещении курсора на расстояние, определенное параметром Threshold.
О включении механизма сигнализирует указатель мыши — он изменяется на курсор, определенный в свойстве
property DragCursor: TCursor;
Еще раз напомним, что источник при перемещении курсора не изменяет собственного положения, и только в случае успешного завершения переноса сможет взаимодействовать с приемником.
Приемником может стать любой компонент, в котором создан метод-обработчик
procedure DragOver(Source: TObject; X, Y: Integer; State: TDragState;
var Accept: Boolean);
Он вызывается при перемещении курсора в режиме Drag-and-Drop над этим компонентом. В методе-обработчике можно предусмотреть селекцию источников переноса по нужным атрибутам.
Если параметр Accept получает значение True, то данный компонент становится приемником. Источник переноса определяется параметром source. Через этот параметр разработчик получает доступ к свойствам и методам источника. Текущее положение курсора задают параметры X и Y. Параметр state возвращает информацию о характере движения мыши:
type TDragState = (dsDragEnter, dsDragLeave, dsDragMove);
dsDragEnter — указатель появился над компонентом; dsDragLeave — указатель покинул компонент; dsDragMove — указатель перемещается по компоненту.
Приемник должен предусматривать выполнение некоторых действий в случае, если источник завершит перенос именно на нем. Для этого используется метод-обработчик
type TDragDropEvent = procedure(Sender, Source: TObject; X, Y: Integer)
of object;
property OnDragDrop: TDragDropEvent;
который вызывается при отпускании левой кнопки мыши на компоненте-приемнике. Доступ к источнику и приемнику обеспечивают параметры Source и Sender соответственно. Координаты мыши возвращают параметры X и Y.
При завершении переноса элемент управления — источник — получает соответствующее сообщение, которое обрабатывается методом
type TEndDragEvent = procedure(Sender, Target: TObject; X, Y: Integer)
of object;
property OnEndDrag: TEndDragEvent;
Источник и приемник определяются параметрами Sender и Target соответственно. Координаты мыши определяются параметрами X и Y.
Для программной остановки переноса можно использовать метод EndDrag источника (при обычном завершении операции пользователем он не используется):
procedure EndDrag(Drop: Boolean);
Параметр Drop = True завершает перенос. Значение False прерывает перенос.
Теперь настало время закрепить полученные знания на практике. Рассмотрим небольшой пример. В проекте DemoDragDrop на основе механизма Drag-and-Drop реализована передача текста между текстовыми редакторами и перемещение панелей по форме (Рисунок 27.1).
Интерфейс присоединения Drag-and-Dock
Интерфейс присоединения Drag-and-Dock
Эта возможность появилась в Delphi 4. Она "подсмотрена" опять-таки у разработчиков из Microsoft, внедривших плавающие панели инструментов в MS Office, Internet Explorer и другие продукты (Рисунок 27.2).
Речь идет о том, что ряд элементов управления (а конкретно — потомки класса xwinControl) могут служить носителями (доками) для других элементов управления с возможностью их динамического перемещения из одного дока в другой при помощи мыши. Перетаскивать можно практически все — от статического текста до форм включительно. Пример использования техники Drag-and-Dock дает сама среда разработки Delphi — с ее помощью можно объединять на экране различные инструменты, такие как Инспектор объектов и Менеджер проекта.
Как и в случае с технологией перетаскивания Drag-and-Drop, возможны два варианта реализации техники Drag-and-Dock: автоматический и ручной. В первом случае дело сводится к установке нужных значений для нескольких свойств, а остальную часть работы берет на себя код VCL; во втором, как следует из названия, вся работа возлагается на программиста.
Итак, что же нужно сделать для внедрения Drag-and-Dock? В Инспекторе объектов необходимо изменить значение свойства DragKind на dkDock, a свойства DragMode — на dmAutomatic. Теперь этот элемент управления можно перетаскивать с одного носителя-дока на другой.
Носителем других компонентов (доком) может служить потомок TwinControl. У него есть свойство Docksite, установка которого в True разрешает перенос на него других компонентов. Если при этом еще и установить свойство AutoSize в True, док будет автоматически масштабироваться в зависимости от того, что на нем находится. В принципе, этими тремя операциями исчерпывается минимальный обязательный набор.
Ярлыки
Ярлыки
Пользовательский интерфейс трудно представить без ярлычков с оперативной подсказкой (Hints). Если задержать курсор, например, над кнопкой или компонентом палитры самой среды Delphi, появляется маленький прямоугольник яркого цвета (окно подсказки), в котором одной строкой сказано о названии этого элемента или связанном с ним действии. Delphi поддерживает механизмы создания и отображения таких ярлычков в создаваемых программах.
Свойство, определяющее активность системы подсказки у элемента управления:
property ShowHint: Boolean;
Если свойство ShowHint установлено в True, и во время выполнения курсор задержался над компонентом на некоторое время, в окне подсказки высвечивается текстовая строка с подсказкой, которая задана свойством:
property Hint: string;
Подсказка компонента может быть пустой строкой — в этом случае система ищет в цепочке первый родительский компонент с непустой подсказкой.
Если в строке Hint встречается специальный символ-разделитель "Г, то часть строки до него ("короткая") передается в окно подсказки, а после ("длинная") — присваивается свойству Hint объекта Application. Ее можно использовать, например, в строке состояния внизу главной формы приложения (см. пример ниже).
Система оперативных подсказок имеет свойства и методы, общие для всех форм в приложении. Не удивительно, что они сосредоточены в Application — глобальном объекте, соответствующем работающему приложению. Все описанные ниже в этом разделе свойства относятся не к компоненту, показывающему подсказку, а именно к Application.
Фоновый цвет окна подсказки можно изменить посредством свойства
property HintColor: TColor;
У объекта Application значение свойства showHint нужно устанавливать во время выполнения, например, в обработчике OnCreate главной формы приложения. Оно является главенствующим для всей системы подсказок: если оно установлено в значение False, ярлычки не возникают.
Есть еще один способ получения подсказки. При смене текущего элемента управления (т. е. при смене текста в свойстве Hint) в объекте Application возникает событие
property OnHint: TNotifyEvent;
Пример:
procedure TForml.AppHint(Sender: TObject);
begin
Panell.Caption:= Application.Hint;:
end;
procedure TForml.FormCreate(Sender: TObject);
begin
Application.OnHint := AppHint;
end;
В этом примере текст подсказки будет отображаться в строке состояния Panell независимо от значения showHint у любого объекта — лишь бы этот текст был в наличии. Для этого разделяйте подсказку у элементов управления вашего приложения на две части при помощи символа " |" — краткая информация появится рядом с элементом, а более полная — в строке состояния.
function GetLongHint(const Hint: string): string; function GetShortHint(const Hint: string): string;
У других компонентов свойство ShowHint интерпретируется системой так: когда курсор мыши останавливается над элементом управления или пунктом меню, и приложение не занято обработкой сообщения, происходит проверка, и если свойство showHint у элемента или у одного из его родительских элементов в иерархии равно True, то начинается ожидание.
Если в данный момент другие ярлычки не показываются, то интервал времени задается свойством HintPause:
property HintPause: Integer;
Интервал времени по умолчанию равен 500 мс. Если в данный момент уже виден ярлычок другого компонента, то интервал времени ожидания задается свойством:
property HintShortPause: Integer;
По истечении этого времени, если мышь осталась над тем же элементом управления, наступает момент инициализации окна подсказки. При этом программист может получить управление, предусмотрев обработчик события объекта Application:
property OnShowHint: TShowHintEvent;
TShowHintEvent = procedure (var HintStr: string;
var CanShow: Boolean;
var Hintlnfo: THintlnfo) of object;
Рассмотрим параметры обработчика события OnShowHint:
Hintstr — отображаемый текст; CanShow — необходимость (возможность) появления подсказки. Если в переменной CanShow обработчик вернет значение False, то окно подсказки высвечиваться не будет; Hintinfo — структура, несущая всю информацию о том, какой элемент управления, где и как собирается показать подсказку. Ее описание:THintlnfo = record
HintControl: TControl;
HintPos: TPoint;
HintMaxWidth: Integer;
HintColor: TColor;
CursorRect: TRect;
CursorPos: TPoint;
end;
Для показа окна подсказки необходимо еще, чтобы у элемента управления или у его предков в цепочке строка Hint была непустой. Впрочем, это можно исправить в обработчике OnShowHint:
procedure TForml.AppShowHint(var HintStr: string;
var CanShow: Boolean;var Hintlnfo: THintlnfo);
begin if HintStr='' then
begin
HintStr := Hintlnfo.HintControl.Name; Hintlnfo.HintColor := clRed; CanShow := True;
end;
end;
Присвоив этот метод обработчику Application.OnShowHint, установив Form.showHint:=True и очистив все строки Hint, получим в качестве подсказки имя каждого элемента.
Длительность показа ярлычка задается свойством
property HintHidePause: Integer;
По умолчанию его значение равно 2500 мс.
Свойство
property HintShortCuts: Boolean;
отвечает за показ вместе с текстом ярлычка описания "горячих" клавиш данного элемента управления.
Наконец, можно вручную "зажечь" и "потушить" ярлычок. При помощи метода
procedure ActivateHint(CursorPos: TPoint);
ярлычок показывается в точке CursorPos (система координат — экранная). "Спрятать" окно подсказки можно с помощью метода:
procedure CancelHint;
Без повторного перемещения мыши на текущий элемент оно более не возникнет.
Плавающие панели инструментов
Рисунок 27.2. Плавающие панели инструментов
![](image/gl-27-2.gif)
Естественно, для программиста предусмотрены возможности контроля за этим процессом. Каждый переносимый элемент управления имеет два события, возникающие в моменты начала и конца переноса:
type TStartDockEvent = procedure(Sender: TObject;
var DragObject: TDragDockObject) of object;
TEndDragEvent = procedure(Sender, Target: TObject; X, Y: Integer) of object;
В первом из методов sender — это переносимый объект, a DragObject — специальный объект, создаваемый на время процесса переноса и содержащий его свойства. Во втором sender — это также переносимый объект, a Target — объект-док.
Док тоже извещается о событиях во время переноса:
type TGetSitelnfoEvent = procedure(Sender: TObject; DockClient: TControl;
var InfluenceRect: TRect; MousePos: TPoint;
var CanDock: Boolean)
of object;
TDockOverEvent = procedure(Sender: TObject; Source: TDragDockObject;
X, Y: Integer; State: TDragState; var Accept: Boolean) of object;
TDockDropEvent = procedure(Sender: TObject;
Source: TDragDockObject;
X, Y: Integer) of object;
TUnDockEvent = procedure(Sender: TObject; Client: TControl; NewTarget:
TWinControl; var Allow: Boolean) of object;
Как только пользователь нажал кнопку мыши над переносимым компонентом и начал сдвигать его с места, всем потенциальным докам (компонентам, свойство которых Docksite установлено в True) рассылается событие onGetsiteinfo. С ним передаются параметры: кто хочет "приземлиться" (параметр Dockclient) и где (MousePos). В ответ док должен сообщить решение, принимает он компонент (параметр CanDock) и предоставляемый прямоугольник (infiuenceRect) или нет. При помощи этого события можно принимать только определенные элементы управления, как показано в примере:
procedure TForml.PanellGetSitelnfо(Sender: TObject; DockClient: TControl; var InfiuenceRect:
TRect; MousePos: TPoint; var CanDock: Boolean);
begin
if DockClient is TBitBtn then CanDock := False;
end;
Два последующих события в точности соответствуют своим аналогам из механизма переноса Drag-and-Drop). Событие onDockOver происходит при перемещении перетаскиваемого компонента над доком, OnDockDrop — в момент его отпускания. Наконец, onUnDock сигнализирует об уходе компонента с дока и происходит в момент его "приземления" в другом месте.
Между доком и содержащимися на нем элементами управления есть двусторонняя связь. Все "припаркованные" элементы управления содержатся в векторном свойстве Dockclients, а их количество можно узнать из свойства
DockClientCount:
s : = ' ' ;
for i := 0 to Panell.DockClientCount-1
do AppendStr(s,Panell.DockClients[i].Name+#$D#$A); ShowMessage(s) ;
С другой стороны, если элемент управления находится на доке, то ссылка на док располагается в свойстве HostDocksite. С ее помощью можно установить, где находится элемент, и даже поменять свойства дока:
procedure TMyForm.ButtonlEndDock(Sender, Target: TObject; X, Y: Integer); begin
(Sender as TControl).HostDockSite.SetTextBuf(pChar((Sender as TControl).Name));
end;
Компоненты можно не только переносить с одного дока на другой, но и отпускать в любом месте. Хотя сам по себе компонент TControl и его потомки не являются окнами Windows, но специально для этого случая создается окно-носитель. Свойство FloatingDockSiteClass как раз и определяет класс создаваемого окна. По умолчанию для большинства компонентов значение этого свойства равно TCustomDockForm. Это — форма, которая обладает свойствами дока и создается в момент отпускания элемента управления вне других доков. Внешне она ничем не отличается от обычной стандартной формы. Если вы хотите, чтобы ваша плавающая панель инструментов выглядела по- особенному, нужно породить потомка от класса TCustomDockForm и связать свойство FloatingDockSiteCiass с этим порожденным классом:
TMyCustomFloatingForm = class(TCustomDockForm)
public
constructor Create(AOwner: TComponent);
override;
end;
constructor TMyCustomFloatingForm.Create(AOwner: TComponent};
begin
inherited Create(AOwner);
BorderStyle := bsNone;
end;
procedure TForml.FormCreate(Sender: TObject);
begin
ToolBarl.FioatingDockSiteCiass := TMyCustomFloatingForm; end;
В этом примере решена типовая задача — сделать так, чтобы несущее окно плавающей панели инструментов не содержало заголовка. Внешний вид таких панелей приведен на Рисунок 27.3.
Переносить компоненты можно не только с помощью мыши, но и программно. Для этого есть пара методов ManualDock и ManualFioat. В приводимом ниже примере нажатие кнопки с именем BitBtnl переносит форму custForm на док MainForm.Paneil и размещает ее по всей доступной площади (параметр выравнивания alclient). Нажатие кнопки BitBtn2 снимает эту форму с дока и выравнивает ее по центру экрана. В свойствах UndockHeight и undockwidth хранятся высота и ширина элемента управления на момент, предшествующий помещению на док:
procedure TMainForm.BitBtnlClick(Sender: TObject);
begin
GustForm.ManualDock
(MainForm.Pane11,nil,alClient);
end;
procedure TMainForm.BitBtn2Click(Sender: TObject);
begin
with CustForm do
begin ManualFloat(Rect((Screen.Width-UndockWidth) div 2,
(Screen.Height-UndockHeight) div 2, (Screen.Width+UndockWidth) div 2, (Screen.Height+UndockHeight) div 2) );
end;
Плавающие панели инструментов без заголовка окна
Рисунок 27.3. Плавающие панели инструментов без заголовка окна
![](image/gl-27-3.gif)
Полное рассмотрение внутреннего устройства механизмов Drag-and-Dock потребовало бы расширения объема этой главы. Тем, кто хочет использовать их на все 100%, рекомендуем обратиться к свойствам useDockManager и DockManager. Последнее представляет собой СОМ-интерфейс, позволяющий расширить возможности дока, вплоть до записи его состояния в поток (класс TStream).
Delphi предоставляет разработчику набор стандартных
Резюме
Delphi предоставляет разработчику набор стандартных программных механизмов, позволяющих добавлять к приложениям функции пользовательского интерфейса Windows. Кроме представленных здесь, разработчик может использовать расширенный набор функций, содержащийся в библиотеке Shell API, которой посвящена гл. 31.
Свойства и методы класса mouse
Таблица 27.1. Свойства и методы класса mouse
Объявление |
Тип |
Описание |
property Capture: HWND; |
Pu |
Дескриптор элемента управления, над которым находится мышь |
property CursorPos: TPoint; |
Pu |
Содержит координаты указателя мыши |
property Draglmmediate: Boolean; |
Ro |
При значении True реакция на нажатие выполняется немедленно |
property DragThreshold: Integer; |
Ro |
Задержка реакции на нажатие |
property MousePresent: Boolean; |
Ro |
Определяет наличие мыши |
type UINT = LongWord; property RegWheelMessage: UINT; |
Ro |
Задает сообщение, посылаемое при прокрутке в ScrollMouse |
property WheelPresent: Boolean; |
Ro |
Определяет наличие ScrollMouse |
property WheelScrollLines : Integer; |
Ro |
Задает число прокручиваемых линий |
Листинг 27.2. Модуль главной формы проекта DemoMouse
unit Main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, ComCtrls;
type
TMainForm = class(TForm) ColorDlg: TColorDialog;
StatusBar: TStatusBar; Timer: TTimer;
procedure FormMouseDown(Sender: TObject;
Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormMouseUp(Sender: TObject;
Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure TimerTimer(Sender: TObject);
private
MouseRect: TRect;
IsDown: Boolean;
RectColor: TColor;
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation {$R *.DFM}
procedure TMainForm.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Button = mbLeft then with MouseRect do
begin
IsDown := True; Left := X; Top := Y; Right := X; Bottom := Y;
Canvas.Pen.Color := RectColor;
end;
if (Button = mbRight) and ColorDlg.Execute then RectColor := ColorDlg.Color;
end;
procedure TMainForm.FormMouseUp(Sender: TObject;
Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
IsDown := False;
Canvas.Pen.Color := Color;
with MouseRect do
Canvas.Polyline([Point(Left, Top), Point(Right, Top), Point(Right,
Bottom), Point(Left, Bottom), Point(Left, Top)]);
with StatusBar do
begin
Panels[4].Text := ''; Panels [5] .Text := ";
end;
end;
procedure TMainForm.FonnMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
with StatusBar do
begin
Panels[2].Text := 'X: ' + IntToStr(X);
Panels[3].Text := 'Y: ' + IntToStr(Y);
end;
if Not IsDown then Exit; Canvas.Pen.Color := Color; with mouserect do
begin
Canvas.Polyline([Point(Left, Top), Point(Right, Top),
Point(Right, Bottom), Point(Left, Bottom), Point(Left, Top)]);
Right := X;
Bottom := Y;
Canvas.Pen.Color := RectColor;
Canvas.Polyline([Point(Left, Top), Point(Right, Top),
Point(Right, Bottom), Point(Left, Bottom), Point(Left, Top)]);
end;
with StatusBar do begin
Panels [4] .Text := 'IHwpMHa: ' + IntToStr(Abs(MouseRect.Right - MouseRect.Left));
Panels[5].Text := 'BacoTa: ' + IntToStr(Abs(MouseRect.Bottom - MouseRect.Top));
end; end;
procedure TMainForm.TimerTimer(Sender: TObject);
begin
with StatusBar do
begin
Panels[0].Text := 'flaTa: ' + DateToStr(Now); Panels[1].Text := 'BpeMH: ' + TimeToStr(Now);
end;
end;
end.
При нажатии левой кнопки мыши в методе-обработчике FormMouseDown включается режим рисования прямоугольника (isDown := True) и задаются его начальные координаты.
При перемещении мыши по форме проекта вызывается метод-обработчик FormMouseMove, в котором координаты курсора и размеры прямоугольника передаются на панель состояния. Если левая кнопка мыши нажата (isDown = True), то осуществляется перерисовка прямоугольника.
При отпускании кнопки мыши в методе FormMouseUp рисование прямоугольника прекращается (isDown := False).
Если была нажата правая кнопка мыши, то метод-обработчик FormMouseDown обеспечивает отображение диалога выбора цвета, который позволяет сменить цвет линий прямоугольника.
Метод-обработчик TimerTimer обеспечивает отображение на панели состояния текущей даты и времени.
Примечание
Примечание
Для рисования прямоугольника использовался метод PolyLine, который работает при перемещении курсора влево и вверх относительно начальной точки. Для стирания старого прямоугольника желательно использовать режимы XOR и NOTXOR, которые обеспечивают восстановление рисунка под линией. Подробно об этом см. гл. 10.
Управление фокусом
Управление фокусом
В процессе работы приложения тот или иной элемент управления получает фокус ввода в зависимости от действий пользователя. Очень часто передача фокуса между элементами управления должна быть упорядочена. Например, при вводе данных в приложениях баз данных пользователь должен иметь максимум удобств для обеспечения хорошей производительности труда. Для этого он должен работать только с клавиатурой, не отвлекаясь на лишние операции по передаче фокуса в нужный компонент при помощи мыши.
Для решения подобного рода проблем все оконные элементы управления имеют два свойства. Свойство TabOrder определяет порядок передачи фокуса между элементами управления одного владельца (формы, панели, группы) при нажатии клавиши <Таb>. Значение 0 имеет компонент, который будет получать фокус при открытии формы.
Для того чтобы свойство TabOrder работало, свойство Tabstop должно иметь значение True.
Кроме этого, все кнопки (произошедшие от TButtonControl) имеют свойство Default, которое при значении True заставляет кнопку реагировать на нажатие клавиши <Enter> как на щелчок на кнопке, даже если она не имеет фокус. Только одна кнопка на форме может иметь это свойство установленным.
Для передачи фокуса любому оконному элементу управления программными средствами можно использовать метод
procedure SetFocus; virtual;
унаследованный от класса TwinControl.
При необходимости работы в форме применяется метод
function SetFocusedControl(Control: TWinControl): Boolean; virtual;
класса TForm, в параметре указывается указатель на компонент, принадлежащий форме.
Управление мышью
Управление мышью
Каждый элемент управления обладает набором свойств и методов, обеспечивающих управление мышью. Понятно, что это важный и нужный механизм. Рассмотрим кратко его устройство.
Воздействие мышью на интерфейсные элементы приложения разработчик может отслеживать при помощи целой группы методов-обработчиков.
На нажатие кнопки мыши реагирует метод
type
TMouseEvent = procedure (Sender: TObject;
Button: TMouseButton;
Shift: TShiftState; X, Y: Integer) of object;
property OnMouseDown: TMouseEvent;
В параметре Button передается признак нажатой кнопки:
type TMouseButton = (mbLeft, mbRight, mbMiddle);
Параметр shift определяет нажатие дополнительной клавиши на клавиатуре:
type TShiftState = set of (ssShift, ssAlt, ssCtrl, ssLeft, ssRight, ssMiddle, ssDouble);
Параметры х и у возвращают координаты курсора.
На отпускание кнопки мыши реагирует метод:
type
TMouseEvent = procedure (Sender: TObject;
Button: TMouseButton;
Shift: TShiftState; X, Y: Integer) of object;
property OnMouseUp: TMouseEvent;
Его параметры описаны выше.
При перемещении мыши можно вызывать метод-обработчик
TMouseMoveEvent = procedure(Sender: TObject;
Shift: TShiftState; X, Y: Integer) of object;
property OnMouseMove: TMouseMoveEvent;
Если у разработчика нет необходимости так подробно отслеживать состояние мыши, можно воспользоваться двумя другими методами:
property OnClick: TNotifyEvent;
property OnDblClick: TNotifyEvent;
Первый реагирует на щелчок кнопкой, второй — на двойной щелчок.
Каждый элемент управления может изменять внешний вид указателя мыши, перемещающейся над ним. Для этого используется свойство
property Cursor: TCursor;
Для управления дополнительными возможностями мыши для работы в Internet (ScrollMouse) предназначены три метода обработчика, реагирующие на прокрутку:
property OnMouseWheel: TMouseWheelEvent;вызывается при прокрутке;
property OnMouseWheelUp: TMouseWheelUpDownEvent;вызывается при прокрутке вперед;
property OnMouseWheelDown: TMouseWheelUpDownEvent;вызывается при прокрутке назад.
В VCL имеется класс TMouse, содержащий свойства мыши, установленной на компьютере. Обращаться к экземпляру класса, который создается автоматически, можно при помощи глобальной переменной Mouse. Свойства класса представлены в табл. 27.1.
В качестве примера обработки управляющих воздействий от мыши рассмотрим пример DemoMouse. Он очень прост. Перемещение мыши с нажатой левой кнопкой обеспечивает выделение прямоугольного фрагмента. Такую функцию вы можете наблюдать в любом графическом редакторе, а исходный код проекта использовать в собственных разработках (листинг 27.2).
Усовершенствованное масштабирование
Усовершенствованное масштабирование
В класс TControl добавлены свойства, позволяющие упростить масштабирование форм и находящихся на них компонентов.
Свойство Anchors:
TAnchorKind = (akLeft, akTop, akRight, akBottom);
TAnchors = set of TAnchorKind; property Anchors: TAnchors;
отвечает за привязку компонентов к определенным краям формы при масштабировании. По умолчанию любой компонент привязан к верхней и левой сторонам ([akLeft, akTop]), т. е. не двигается при стандартном масштабировании. Но, изменив значение этого свойства, можно сделать так, чтобы компонент находился, к примеру, все время в нижнем правом углу.
С другой стороны, если прикрепить все четыре стороны, то получится интересный и нужный во многих случаях эффект. Такой компонент увеличивается и уменьшается вместе с формой; но в то же время сохраняется расстояние до всех четырех ее краев.
Свойство constraints представляет собой набор ограничений на изменение размеров компонента. Оно содержит четыре свойства: MaxHeight, Maxwidth, MinHeight и Minwidth. Как легко догадаться из названий, размеры компонента могут меняться только в пределах значений этих четырех свойств.
Наконец, большинство элементов управления получили свойство Autosize, позволяющее им автоматически масштабироваться при изменении содержимого (скажем, надписи на кнопке).
Диалог команды Parameters меню Run
Рисунок 28.1. Диалог команды Parameters меню Run
![](image/gl-28-1.gif)
Диалог мастера библиотеки ресурсов для включения в проект дополнительных файлов
Рисунок 28.5. Диалог мастера библиотеки ресурсов для включения в проект дополнительных файлов
![](image/gl-28-5.gif)
Диалог мастера библиотеки ресурсов со списком доступных языков локализации проекта
Рисунок 28.3. Диалог мастера библиотеки ресурсов со списком доступных языков локализации проекта
![](image/gl-28-3.gif)
Если запускаемое приложение или DLL находит в своей папке одноименный файл с расширением, которое соответствует одной из возможных локализаций и эта локализация применяется в системе, то приложение использует ресурсы из этой библиотеки вместо собственных. Поэтому при определении имени файла библиотеки ресурсов категорически не рекомендуется изменять предложенное мастером имя.
Кроме того, папку с файлом ресурсов можно задать дополнительно. Для этого на следующей странице мастера необходимо указать нужную папку для каждого языкового ресурса или принять предложенную по умолчанию (Рисунок 28.4).
Затем вы можете добавить к библиотеке ресурсов собственные файлы. Это могут быть ресурсы любого рода, используемые приложением. В окне мастера (Рисунок 28.5) необходимо выбрать эти файлы.
В последующих диалогах мастера задается способ создания или обновления для каждого языкового ресурса и запускается процесс создания ресурса. При первоначальном создании DLL ресурсы можно только создавать, впоследствии их можно полностью перезаписывать или изменять.
По завершении работы мастера для каждого выбранного языка создается новый проект библиотеки ресурсов. Результат работы мастера выводится в информационном окне (Рисунок 28.6).
Рисунок 28.4. Диалог мастера библиотеки ресурсов со списком папок для ресурсов локализации проекта
![](image/gl-28-4.gif)
Диалог мастера библиотеки ресурсов со списком форм, включаемых в проект
Рисунок 28.2. Диалог мастера библиотеки ресурсов со списком форм, включаемых в проект
![](image/gl-28-2.gif)
После этого в третьем диалоге мастера необходимо выбрать один или несколько языков локализации ресурсов (Рисунок 28.3). От этого выбора языка зависит расширение откомпилированного файла библиотеки и алгоритм поведения базового проекта при загрузке.
Директива cdecl
Директива cdecl
Реализует вызовы в стиле языка С. Параметры в стек помещаются справа налево. Очистка стека осуществляется вызывающей процедурой. Такие вызовы обеспечивают обслуживание переменного числа параметров, но скорость обработки меньше, чем в вызовах при реализации директивы pascal.
Эта директива в основном применяется для обращения к динамическим библиотекам, использующим соглашения о вызовах в стиле языка С. Использование директивы cdecl для библиотек Delphi не вызовет ошибку компиляции, но переменное число параметров не обеспечит.
Директива pascal
Директива pascal
Реализует вызовы в стиле языка Pascal. За очистку стека отвечает вызываемая процедура. Параметры помещаются в стек слева направо. Этот способ вызова является очень быстрым, но не поддерживает переменное число параметров. Используется для обеспечения обратной совместимости.
Директива register
Директива register
Эта директива используется по умолчанию. Поэтому нет необходимости добавлять ключевое слов register после объявления функции. Вызов такого типа называется быстрым (fast call). В нем используются три расширенных регистра процессора, в которые помещаются переменные длиной не более 32-х разрядов и указатели. Остальные параметры помещаются в стек слева направо. После использования стек очищается вызываемой процедурой.
Директива safecall
Директива safecall
Параметры помещаются в стек справа налево. Очистка стека осуществляется вызываемой процедурой. Используется в СОМ и основанных на ней технологиях.
Директива stdcall
Директива stdcall
Параметры помещаются в стек слева направо. Очистка стека осуществляется вызываемой процедурой. Этот вызов обеспечивает обработку фиксированного числа параметров.
Компоненты Rave Reports и отчеты в приложении Delphi
ГЛАВА 28
Динамические библиотеки
Динамические библиотеки (DLL, Dynamic Link Library) играют важную роль в функционировании ОС Windows и прикладных программ. Они представляют собой файлы с откомпилированным исполняемым кодом, который используется приложениями и другими DLL. Реализация многих функций ОС вынесена в динамические библиотеки, которые используются по мере необходимости, обеспечивая тем самым экономию адресного пространства. DLL загружается в память только тогда, когда к ней обращается какой-либо процесс.
По существу динамические библиотеки отличаются от исполняемых файлов только одним, они не могут быть запущены самостоятельно. Для того чтобы динамическая библиотека начала работать, необходимо, чтобы ее вызвала уже запущенная программа или работающая DLL.
Обычно в динамические библиотеки выносятся группы функций, которые применяются для решения сходных задач. Кроме этого, в них можно хранить и использовать разнообразные ресурсы — от строк локализации до форм.
Динамическая библиотека может использоваться несколькими приложениями, при этом не обязательно, чтобы все они были созданы при помощи одного языка программирования.
Разновидностью динамических библиотек являются пакеты Delphi, предназначенные для хранения кода компонентов для среды разработки и приложений.
Применение динамических библиотек позволяет добиться ряда преимуществ:
уменьшается размер исполняемого файла приложения и занимаемые им ресурсы; функции DLL могут использовать несколько процессов одновременно; управление динамическими библиотеками возлагается на операционную систему; внесение изменений в DLL не требует перекомпиляции всего проекта; одну DLL могут использовать программы, написанные на разных языках.При разработке динамических библиотек в среде Delphi удобно использовать группу проектов, которая включает проект приложения и проекты динамических библиотек.
В этой главе рассматриваются следующие вопросы:
структура файла DLL; инициализация DLL; явная и неявная загрузка; вызовы функций из динамической библиотеки; ресурсы в динамических библиотеках.Инициализация и завершение работы DLL
Инициализация и завершение работы DLL
При загрузке динамической библиотеки выполняется код инициализации, который расположен в блоке begin, .end (см. листинги 28.1 и 28.2). Обычно здесь выполняются операции по заданию начальных значений используемых в функциях библиотеки переменных, проверка условий функционирования DLL, создание необходимых структур и объектов и т. д.
При возникновении ошибок выполнения кода инициализации можно воспользоваться специальной глобальной переменной Exitcode из модуля System. Если при возникновении исключительной ситуации присвоить этой переменной любое ненулевое значение, загрузка библиотеки прерывается.
Примечание
Примечание
Любые объявленные в DLL глобальные переменные недоступны за ее пределами.
Оказывается, что при загрузке динамической библиотеки в адресное пространство вызывавшего ее процесса, происходят важные события, знание которых позволит вам эффективно управлять инициализацией и выгрузкой DLL.
Итак, перед запуском кода инициализации автоматически вызывается встроенная ассемблерная процедура _initDLL (она расположена в модуле system). Она сохраняет состояние регистров процессора; получает значение экземпляра модуля библиотеки и записывает его в глобальную переменную hinstance; устанавливает для глобальной переменой isLibrary значение True (по этому значению вы всегда сможете распознать код DLL); получает из стека ряд параметров; проверяет переменную процедурного типа DLLProc:
var DLLProc: Pointer;
Эта переменная используется для проверки вызовов операционной системой точки входа DLL. С этой переменной можно связать процедуру с одним целочисленным параметром. Такая процедура называется функцией обратного вызова системного уровня.
Если при проверке переменной DLLProc процедура _initDLL находит связанную функцию обратного вызова, то она вызывается. При этом ей передается параметр, полученный из стека. В качестве параметра могут быть переданы четыре значения:
const
DLL_PROCESS_DETACH = 0;
DLL_PROCESS_ATTACH = 1;
DLL_THREAD_ATTACH = 2;
DLL_THREAD_DETACH = 3;
Рассмотрим их.
Значение DLL_PROCESS_DETACH передается при выгрузке DLL из адресного пространства процесса. Это происходит при явном вызове системной функции FreeLibrary (см. ниже) или при завершении процесса. Значение DLL_PROCESS_ATTACH означает, что библиотека отображается в адресное пространство процесса, который загружает ее в первый раз. Значение DLL_THREAD_ATTACH посылается всем загруженным в процесс динамическим библиотекам при создании нового потока. Обратите внимание, что при создании процесса и первичного потока посылается только одно значение DLL_PROCESS_ATTACH. Значение DLL_THREAD_DETACH посылается всем загруженным в процесс динамическим библиотекам при уничтожении существующего потока.Впоследствии, при работе процесса с загруженной DLL, в случае возникновения описанных событий, функция обратного вызова вызывается снова и снова. При этом ей передается одно из рассмотренных значений.
Это хороший способ организовать в динамической библиотеке необходимую в каждом случае обработку. Как это сделать?
Во-первых, необходимо создать процедуру, подходящую для процедурного типа DLLProc, и написать для нее исходный код, применяемый в зависимости от переданного параметра.
Во-вторых, в секции инициализации нужно связать переменную DLLProc и созданную процедуру.
Применительно к рассматриваемому нами примеру, модернизированный исходный код библиотеки DataCheck будет выглядеть так:
Листинг 28.3. Часть исходного кода динамической библиотеки DataCheck c функцией обратного вызова
...
{Часть исходного кода опущена (см. листинг 24.2)}
exports
IsValidlnt,
IsValidDate index 1,
IsValidTime index 2 name 'ValidTime',
procedure DLLEntryPoint(Reason: Integer);
begin
case Reason of
DLL_PROCESS_ATTACH: ShowMessage('Первая загрузка DLL'); DLL_PROCESS_DETACH:;
DLL_THREAD_ATTACH: ShowMessage('Создан новый поток'); DLL_THREAD_DETACH:; end; end;
begin
DLLProc := @DLLEntryPoint;
DLLEntryPoint(DLL_PROCESS_ATTACH);
end.
Процедура DLLEntryPoint обеспечивает простой показ сообщения о полученном значении параметра. В коде инициализации глобальной переменной DLLProc передается адрес процедуры DLLEntryPoint. Затем эта процедура вызывается явно с параметром DLL_PROCESS_ATTACH.
У недоверчивого читателя может возникнуть вопрос — а зачем городить такие сложности, если можно просто использовать код в секции инициализации? Дело в том, что этот код выполняется только при запуске DLL. Поэтому, как, например, вовремя уничтожить создаваемые в библиотеке объекты при завершении ее работы? Для этого можно использовать функцию обратного вызова:
Листинг 28.4. Создание и удаление объекта при загрузке и выгрузке динамической библиотеки DataCheck .
...
(Часть исходного кода опущена (см. листинг 24.2)}
exports
IsValidlnt,
IsValidDate index 1,
IsValidTime index 2 name 'ValidTime',
type TSomeObject = class(TObject)
Fieldl: String; end; var FirstObj: TSomeObject;
procedure DLLEntryPoint(Reason: Word);
begin
case Reason of DLL_PROCESS_ATTACH:
begin
FirstObj := TSomeObject.Create; FirstObj.Fieldl := 'Объект создан'; ShowMessage(FirstObj.Fieldl);
end;
DLL__PROCESS_DETACH: FirstObj . Free;
DLL_THREAD_ATTACH: ShowMessage('Создан новый поток'); DLL_THREAD_DETACH:;
end;
end;
begin
DLLProc := @DLLEntryPoint;
DLLEntryPoint(DLL_PROCESS_ATTACH);
end.
При завершении работы динамической библиотеки вызывается процедура, на которую указывает адрес, содержащийся в переменной ExitProc:
var ExitProc: Pointer;
Использование модуля ShareMem
Использование модуля ShareMem
Если динамическая библиотека в процессе работы использует переменные или функции, осуществляющие динамическое выделение памяти под собственные нужды (длинные строки, динамические массивы, функции New и GetMem), а также, если такие переменные передаются в параметрах и возвращаются в результатах, то в таких библиотеках обязательно должен использоваться модуль ShareMem. При этом в секции uses модуль должен располагаться на первом месте. Об этом напоминает комментарий, автоматически добавляемый в файл динамической библиотеки при создании (см. листинг 28.1).
Управление этими операциями осуществляет специальный диспетчер печати BORLANDMM.DLL. Он должен распространяться вместе с динамическими библиотеками, использующими модуль ShareMem.
Явный вызов
Явный вызов
Явный вызов динамической библиотеки подразумевает создание программистом соответствующего исходного кода. Ему необходимо предусмотреть загрузку DLL, получение адресов переменных процедурного типа для используемых функций и процедур, выгрузку DLL.
Пример явного вызова функций динамической библиотеки имеется в демонстрационном приложении DemoDLL2, которое по выполняемым функциям полностью совпадает с предыдущим примером.
Листинг 28.6. Модуль главной формы проекта DemoDll2
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type
StandardProc = function(AText: String): Boolean;
TMainForm = class(TForm)
Editl: TEdit;
Edit2: TEdit;
Edit3: TEdit;
Label1: TLabel;
Label2: TLabel;
LabelS: TLabel;
procedure FormShow(Sender: TObject);
procedure EditlExit(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Edit2Exit(Sender: TObject);
procedure EditSExit(Sender: TObject);
private
DLLHandle: THandle;
LoadError: Word;
IsValidlnt: StandardProc;
IsValidDate: StandardProc;
ValidTime: StandardProc;
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation {$R *.DFM}
procedure TMainForm.FormShow(Sender: TObject);
begin
DLLHandle := LoadLibrary('DataCheck');
if DLLHandle = 0 then begin if GetLastError = ERROR_DLL_NOT_FOUND
then ShowMessagef'Ошибка загрузки DLL');
Close;
end;
@IsValidInt := GetProcAddress(DLLHandle, 'IsValidlnt');
SIsValidDate := GetProcAddress(DLLHandle, 'IsValidDate');
SValidTime := GetProcAddress(DLLHandle, 'ValidTime');
end;
procedure TMainForm.FormClose(Sender: TObject;
var Action: TCloseAction);
begin if DLLHandle <> 0
then FreeLibrary(DLLHandle);
end;
procedure TMainForm.EditlExit(Sender: TObject);
begin
if not IsValidlnt(Editl.Text)
then Edit2.Clear;
end;
procedure TMainForm.Edit2Exit(Sender: TObject);
begin if not IsValidDate(Edit2.Text)
then Editl.Clear;
end;
procedure TMainForm.EditSExit(Sender: TObject);
begin
if not ValidTime(Edit3.Text)
then Edit3.Clear;
end;
end.
Загрузка динамической библиотеки DataCheck осуществляется в методе-обработчике FormShow при помощи функции LoadLibrary. Имя динамической библиотеки может не содержать маршрута, если файл DLL расположен в одном каталоге с программой. Если в этом каталоге файл DLL не найден, поиск последовательно проводится в текущем каталоге, \SYSTEM и каталогах из перечня Path.
Так как для этой системной функции не создается исключительная ситуация, то следом предусмотрен контроль возможных ошибок. Функция GetLastError возвращает код последней ошибки.
Примечание
Примечание
Код ошибки ERROR_DLL_NOT_FOUND, Наряду со многими другими кодами, содержится в файле Windows.PAS.
Если библиотека успешно загружена, в три процедурные переменные типа standardProc передаются адреса соответствующих функций DLL. Процедурный тип standardProc объявлен перед классом формы. Для этого используется системная функция GetProcAddress.
В дальнейшем созданные таким образом функции применяются для вводимых значений в компонентах TEdit.
При закрытии приложения необходимо выгрузить все используемые динамические библиотеки Припомощи системной функции FreeLibrary.
Экспорт из DLL
Экспорт из DLL
Для создания перечня экспортируемых из динамической библиотеки процедур и функций используется ключевое слово exports. При этом можно указывать как функции, описанные в главном файле DLL, так и функции из присоединенных модулей.
В качестве примера рассмотрим исходный код динамической библиотеки DataCheck, простейшие функции которой проверяют введенную строку перед конвертацией на соответствие одному из типов данных.
Листинг 28.2. Исходный код динамической библиотеки DataCheck
library DataChek
uses
Windows, SysUtils, Classes, Messages, Forms,
Dialogs, StdCtrls, ComCtrls;
function ValidDate(AText: String): Integer;
begin
try
Result := 0; StrToDate(AText);
except
on E:EConvertError do Result := -1;
end;
end;
function ValidTime(AText: String): Integer;
begin
try
Result := 0; StrToTime(AText);
except
on E:EConvertError do Result := -1;
end;
end;
function Validlnt(AText: String): Integer;
begin
try
Result := 0; StrToInt(AText);
except
on E:EConvertError do Result := -1;
end;
end;
exports Validlnt,
ValidDate index 1, ValidTime index 2 name 'IsValidTime';
begin
if Length(DateToStr(Date)) < 10
then ShowMessage('Год представлен двумя цифрами');
end.
Итак, три функции этой библиотеки обеспечивают проверку строки перед преобразованием ее в целое число, дату или время. Для обеспечения экспорта этих функций их необходимо объявить в секции exports.
При компиляции библиотеки адрес, имя и порядковый номер экспортируемой функции добавляется к специальной таблице экспорта в файле DLL.
Примечание
Примечание
Компилятор Delphi без проблем добавит таблицу экспорта и к исполняемому файлу приложения. Правда, при этом получить доступ к такой функции невозможно — это системное ограничение Windows.
Попробуйте объявить пару функций после ключевого слова exports в обычном приложении — проект компилируется без ошибок. Но сами функции недоступны другим процессам.
Имена процедур и функций в секции экспорта разделяются запятыми. Внимательный взгляд на пример экспорта в листинге 28.2 обнаруживает три различных варианта объявления.
В первом варианте компилятор самостоятельно определяет положение функции в таблице экспорта.
При использовании ключевого слова index следующее за ним число задает положение функции в таблице экспорта относительно других таких же функций.
Ключевое слово name позволяет экспортировать функцию под другим именем.
Неявный вызов
Неявный вызов
Механизм неявного вызова наиболее прост, т. к. выполняется автоматически и основан на имеющейся в приложении информации о вызываемых функциях и динамических библиотеках. Однако разработчик не имеет возможности влиять на ход загрузки DLL. Если операционная система не смогла загрузить библиотеку, просто выдается сообщение об ошибке. Единственный способ повлиять на процесс загрузки — использовать секцию инициализации библиотеки (см. выше).
В качестве примера неявного вызова рассмотрим простое приложение DemoDLLl, использующее функции библиотеки DataCheck (см. выше). Для этого в нем имеются три компонента TEdit, в которых осуществляется проверка введенной строки на соответствие формату одного из типов данных.
Примечание
Примечание
Проекты DemoDLL1 и DataCheck объединены в одну группу. Переключение между проектами легко выполняется утилитой Диспетчер проектов.
Листинг 28.5.Модуль главной формы проекта DemoDLL1
unit Unitl;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, comctrls, Buttons;
type
TMainForm = class(TForm)
Editl: TEdit;
Edit2: TEdit;
Edit3: TEdit;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
procedure EditlExit(Sender: TObject);
procedure Edit2Exit(Sender: TObject);
procedure EditSExit(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
function IsValidlnt(AText: String): Boolean; external 'DataCheck.dll';
function IsValidDate(AText: String): Boolean; external 'DataCheck.dll';
function ValidTime(AText: String): Boolean; external 'DataCheck.dll';
implementation {$R *.DFM}
procedure TMainForm.EditlExit(Sender: TObject);
begin if not IsValidlnt(Editl.Text)
then Editl.Clear;
end;
procedure TMainForm.Edit2Exit(Sender: TObject);
begin
if not IsValidDate(Edit2.Text)
then Edit2.Clear; end;
procedure TMainForm.Edit3Exit(Sender: TObject);
begin if not ValidTime(Edits.Text)
then EditS.Clear;
end;
end.
Для организации неявного вызова достаточно объявить нужную функцию с директивой external и указать имя содержащей ее динамической библиотеки. Обратите внимание, что третья функция объявлена под псевдонимом isValidTime, который объявлен для этой функции при помощи ключевого слова name в исходном коде динамической библиотеки.
В дальнейшем импортированные функции используются обычным образом.
Окно с информацией о результате создания ресурса
Рисунок 28.6. Окно с информацией о результате создания ресурса
![](image/gl-28-6.gif)
Каждый созданный проект необходимо откомпилировать и включить в состав дистрибутива базового приложения.
Проект DLL
Проект DLL
Для создания динамической библиотеки в Репозитории Delphi имеется специальный шаблон. Его значок DLL Wizard расположен на странице New Репозитория. В отличие от проекта обычного приложения, проект DLL состоит всего из одного исходного файла. Впоследствии к нему можно добавлять отдельные модули и формы.
Листинг 28.1. Исходный файл проекта динамической библиотеки
library Projectl;
{ Important note about DLL memory management: ShareMem must be the first unit in your library's USES clause AND your project's (select Project-View Source) USES clause if your DLL exports any procedures or functions that pass strings as parameters or function results. This applies to all strings passed to and from your DLL-even those that are nested in records and classes. ShareMem is the interface unit to the BORLNDMM.DLL shared memory manager, which must be deployed along with your DLL. To avoid using BORLNDMM.DLL, pass string information using PChar or ShortString parameters. }
uses
SysUtils, Classes;
{$R *.res}
begin
end.
Примечание
Примечание
Обширный комментарий в каждом проекте DLL касается использования модуля ShareMem. О нем рассказывается ниже.
Для определения типа проекта используется ключевое слово library (вместо program в обычном проекте). При компиляции такого проекта динамической библиотеки создается файл с расширением dll.
Как и в любом другом проекте, в проекте динамической библиотеки можно использовать иные модули. Это могут быть просто модули с исходным кодом и модули форм. При этом динамическая библиотека может экспортировать функции, описанные не только в главном файле, но и в присоединенных модулях.
Блок begin..end называется блоком инициализации библиотеки и предназначен для размещения кода, который автоматически выполняется при загрузке DLL.
Между секцией uses и блоком инициализации можно располагать исходный код функций динамической библиотеки и их объявления. При этом можно использовать любые конструкции языка Object Pascal, а также применять формы и компоненты.
Примечание
Примечание
При создании динамических библиотек очень удобно использовать группы проектов. В группу помещается проект приложения и проект (проекты) необходимой для его работы динамической библиотеки (библиотек). Для переключения между проектами удобно использовать Диспетчер проектов (команда Project Manager из меню View). Его можно поместить в окно Редактора кода.
Еще один способ удобной работы с проектами динамических библиотек заключается в задании для DLL вызывающей программы. Это делается в диалоге команды Parameters из меню Run (Рисунок 28.1). Вызывающее приложение задается в группе Host Application. В результате после компиляции динамической библиотеки вызывается использующее ее приложение.
Для того чтобы приложения могли применять функции динамической библиотеки, необходимо, во-первых, экспортировать их из DLL; во-вторых, объявить функции в самом приложении как внешние. Ниже рассматриваются способы решения этих задач.
Ресурсы в DLL
Ресурсы в DLL
Динамические библиотеки могут содержать не только исполняемый код, проводящий некоторые вычисления, но и ресурсы. Чаще всего бывает необходимо распространять вместе с DLL формы, обеспечивающие работу процедур и функций. Приемы работы с формами в проектах динамических библиотек ничем не отличаются от тех же приемов в проектах обычных приложений.
Единственная особенность заключается в том, что любая форма в DLL должна рассматриваться как создаваемая вручную, а не автоматически. При этом в процедуру, создающую форму, должен быть передан указатель на владельца будущей формы.
Например, процедура showDemoForm из рассматриваемой нами библиотеки DataCheck, выглядит так:
procedure ShowDemoForm(AOwner: TComponent);
begin
DemoForm := TDemoForm.Create(AOwner);
DemoForm.ShowModal;
DemoForm.Free;
end;
Уничтожение формы можно организовать не только в самой процедуре, но и (при неоднократном применении) в другой процедуре или при выгрузке динамической библиотеки.
При вызове этой процедуры из приложения в параметре необходимо указать экземпляр класса приложения:
procedure ShowDemoForm(AOwner: TComponent); external 'DataCtrl.dll';
procedure TMainForm.BitBtnlClick(Sender: TObject);
begin
ShowDemoForm(Application);
end;
Обратите внимание, что в данном случае форма из динамической библиотеки рассматривается операционной системой как отдельная задача, о чем свидетельствует системная панель задач.
Для распространения с приложением можно создавать специальные динамические библиотеки ресурсов, которые используются для локализации приложений. Например, в библиотеку ресурсов можно вынести все строковые константы (сообщения, тексты и т. д.), а с приложением распространять динамическую библиотеку ресурсов, строки в которой соответствуют языковым запросам заказчика.
Создать такую библиотеку можно, использовав Репозиторий Delphi (страница New) для проекта приложения или динамической библиотеки. Мастер создания библиотеки ресурсов проводит разработчика через все этапы создания проекта библиотеки.
Примечание
Примечание
Для каждого языка необходимо создавать свои варианты форм и новый проект библиотеки ресурсов.
Перед началом создания проекта библиотеки ресурсов необходимо сохранить и откомпилировать базовый проект (для него создается проект локализации), а затем начать новый проект библиотеки ресурсов.
Первый диалог мастера библиотеки ресурсов предоставляет справочную информацию.
Второй — позволяет создать список форм базового проекта, которые войдут в библиотеку (Рисунок 28.2). При этом можно удалить из списка ненужные формы и добавить необходимые из других проектов.
Резюме
Динамические библиотеки широко используются в ОС Windows. При их применении исполняемые файлы приложений становятся существенно меньше. К одной динамической библиотеке могут обращаться несколько программ одновременно. При этом динамические библиотеки могут использовать весь арсенал программных средств Delphi.
Соглашения о вызовах
Соглашения о вызовах
При объявлении процедур и функций в динамических библиотеках используются различные соглашения о вызовах. Дело в том, что различные языки программирования по-разному реализуют передачу параметров в процедуру (через стек или регистры). Порядок следования параметров в стеке как раз определяется соглашением о вызовах.
Стандартный вызов в языках C++ и Object Pascal различается, но набор директив смены типа вызова позволяет обеспечить любую реализацию.
Во всех соглашениях о вызовах вызывающая процедура помещает параметры в стек. В зависимости от типа соглашения, очистка стека осуществляется вызывающей или вызываемой процедурой.
Если очистка стека выполняется вызывающей процедурой, то она успевает забрать из него возвращаемые значения.
Если очистка стека осуществляется вызываемой процедурой, то перед этим она помещает возвращаемые значения во временную область памяти.
Примечание
Примечание
Помимо рассмотренных ниже директив имеются еще три типа вызовов, которые не используются и сохранены для обеспечения обратной совместимости. Это директивы near, far, export.
Вызов DLL
Вызов DLL
Теперь рассмотрим, как из динамических библиотек вызываются функции.
При запуске исполняемого файла приложения операционная система создает для его работы отдельный процесс. Также система создает первичный поток, владельцем которого является процесс. Процесс приложения получает 4 Гбайт адресного пространства, в которое отображается исполняемый код приложения.
После этого из исполняемого кода извлекается информация обо всех вызываемых приложением динамических библиотеках и их функциях. Эта информация основывается на анализе исходного кода компоновщиком Delphi, который включает в исполняемый файл имена функций и динамических библиотек. При этом используется неявный вызов, описываемый ниже.
В результате при обращении приложения к функции из DLL вся информация о ней уже имеется в процессе. Для выполнения функции (вызов осуществляется одним из потоков процесса приложения) в адресное пространство процесса приложения загружается соответствующая динамическая библиотека. После этого исполняемый код DLL становится полностью доступен внутри процесса, но не вне его. Другие Процессы могут загрузить эту же библиотеку и использовать ее образ в собственном адресном пространстве. Именно поэтому несколько приложений могут применять одну динамическую библиотеку одновременно.
Каждый поток имеет собственный стек, в который загружаются параметры функций DLL и все необходимые локальные переменные. Дело в том, что динамические библиотеки не имеют собственной кучи и не могут владеть данными. Поэтому любые создаваемые функциями DLL данные или объекты принадлежат вызывавшему потоку.
Функции динамических библиотек могут вызываться двумя способами — явным и неявным. Рассмотрим их.