Компоненты Rave Reports и отчеты в приложении Delphi
Форма состояния процесса вывода отчета компонента TRvSystem
Рисунок 23.5. Форма состояния процесса вывода отчета компонента TRvSystem

В нем отображается информационная строка состояния, которая может быть настроена при помощи свойств объекта SystemFiler, представленного в компоненте TRvSystem одноименным свойством.
Вложенное свойство
property StatusFormat: string;
определяет строку форматирования для текста о состоянии процесса. Для нее предусмотрены следующие управляющие символы:
property StatusText: TStrings;
позволяет задать до десяти строк (можно задать и больше, но они не будут восприняты строкой статуса) с какой-либо дополнительной информацией, описывающей процесс вывода. Первая строка списка будет выведена при наличии в свойстве statusFormat управляющего символа %0, вторая — при наличии символа %1 и т. д.
При помощи перечисленных свойств вы сможете детально описать процесс вывода отчета. В этом вам помогут методы-обработчики событий компонента TRvSystem.
До начата печати отчета и по его окончании (даже если печать была прервана) соответственно вызывается пара методов-обработчиков:
property OnBeforePrint: TNotifyEvent;
property OnAfterPrint: TNotifyEvent;
В начале печати непосредственно отчета (не заголовка) вызывается метод-обработчик
property OnPrint: TNotifyEvent;
Если вы печатаете одну страницу, будет вызван метод-обработчик
type
TPrintPageEvent = function(Sender: TObject;
var PageNum: Integer): Boolean;
property OnPrintPage: TPrintPageEvent;
Но до начала печати вызывается метод-обработчик
property OnNewPage: TNotifyEvent;
который обозначает генерацию страницы.
При печати колонтитулов в верхней и нижней частях страницы вызываются методы-обработчики
property OnPrintHeader: TNotifyEvent;
property OnPrintFooter: TNotifyEvent;
Разработчик может задать несколько опций для всего компонента TRvSystem, управляя тем самым процессом вывода отчета. Для это используется свойство
type
TSystemOption = (soUseFiler, soWaitForOK, soShowStatus, soAllowPrintFromPreview, soPreviewModal);
TSystemOptions = set of TSystemOption;
property SystemOptions: TSystemOptions;
Элементы типа TSystemOptions обозначают следующее:
в качестве основного средства для
Генератор отчетов Rave Reports 5.0
Генератор отчетов Rave Reports 5.0 разработан фирмой Nevrona и входит в состав Delphi 7 в качестве основного средства для создания отчетов. Он состоит из трех частей:
Безусловно, визуальная среда разработки заметно упрощает процесс создания отчетов и позволяет добиться лучших результатов меньшими усилиями, чем в генераторе отчетов Quick Report, который использовался в предыдущих версиях Delphi. Тем не менее при первом знакомстве с продуктом заметны и его недостатки. Система помощи оставляет тягостное впечатление не только своей крайней лаконичностью, но и фактическими ошибками. Многие свойства и методы остались недокументированными, и наоборот — имеющиеся в статьях подсказки описания не имеют реальных аналогов в коде компонентов.
Однако будем надеяться, что недостатки будут со временем исправлены. А мы займемся детальным знакомством с процессом создания отчетов.
/H2> br>. Диалог настройки печати компонента TRvSystem Его заголовок определяется свойством
property TitleSetup: TFormatString;
Перед открытием этого окна вызывается метод-обработчик
property OnPreviewSetup: TNotifyEvent;
Кроме этого, для диалога настройки печати можно задать ряд дополнительных параметров. Это делается в свойстве
type
TSystemSetup = (ssAllowSetup, ssAllowCopies, ssAllowCollate,
ssAllowDuplex, ssAllowDestPreview, ssAllowDestPrinter, ssAllowDestFile,
ssAllowPrinterSetup);
TSystemSetups = set of TSystemSetup;
property SystemSetups: TSystemSetups;
Элементы множества TSystemSetup означают следующее:
property TitleStatus: TFormatString;
Компонент отчета TRvProject
Компонент отчета TRvProject
Компонент TRvProject обеспечивает представление в приложении отчета. Для того чтобы связать проект отчета Rave Reports с компонентом, используется свойство
property ProjectFile: string;
До начала печати необходимо связать компонент TRvProject с компонентом управления отчетом TRvSystem. Для этого достаточно передать в свойстве
property Engine: TRpComponent;
ссылку на компонент TRvSystem.
При необходимости вы можете загрузить отчет из внешнего файла или потока:
procedure LoadFromFile(FileName: String);
procedure LoadFromStream(Stream: TStream);
Загруженный отчет становится текущим.
Кроме этого существует и пара методов для сохранения отчета:
function SaveToFile(FileName: String);
procedure SaveToStream(Stream: TStream);
В процессе работы приложения может потребоваться напечатать несколько различных отчетов. Для этого можно использовать требуемое число компонентов TRvProject или загружать нужные отчеты по мере необходимости.
Забегая немного вперед (см. гл. 24), скажем, что один файл проекта отчета может содержать несколько независимых отчетов. Каждый из них идентифицируется в компоненте TRvProject тремя свойствами. Имя, полное имя и описание отчета содержатся соответственно в трех свойствах только для чтения:
property ReportName: String;
property ReportFullName: String;
property ReportDesc: String;
При этом эти три свойства возвращают параметры текущего отчета. Сразу после загрузки из файла текущим становится отчет, являющийся отчетом по умолчанию в среде разработки. При необходимости сменить текущий отчет используется метод
function SelectReport(ReportName: string; FullName: boolean): boolean;
В параметре ReportName передается имя нужного отчета. Если параметр FullName имеет значение True, то это полное имя отчета, иначе — имя отчета.
В случае, если проект содержит несколько отчетов, их имена доступны при помощи метода
procedure GetReportList(ReportList: TStrings;FullName: boolean);
Список имен будет возвращен в список строк ReportList, а параметр FullName определяет, какие именно имена будут занесены в список. При значении параметра True метод возвращает полные имена отчетов (соответствует свойству ReportFullName), иначе — имена (соответствует свойству ReportlName).
Например, код
var ReportList: TStringList; i: Integer;
ReportList := TStringList.Create;
RvProjectl.Open;
try
RvProjectl.GetReportList(ReportList, False);
for i := 0 to ReportList.Count - 1
do RvProjectl.ExecuteReport(ReportList[I]);
finally
RvProjectl.Close;
ReportList.Free;
end;
последовательно печатает все отчеты, входящие в состав файла проекта отчета.
Файл проекта отчета можно включить в состав исполняемого файла приложения. Для этого используется свойство
property StoreRAV: Boolean;
При щелчке на кнопке в строке этого свойства в Инспекторе объектов открывается специализированный редактор Load Into exe (Рисунок 23.2).
Компонент управления отчетом TRvSystem
Компонент управления отчетом TRvSystem
Компонент управления отчетом TRvSystem обеспечивает выполнение основных операций с отчетом из приложения. В приложении он должен быть связан с компонентом TRvProject (см. выше разд. "Компонент отчета TRvProject" данной главы). Этого вполне достаточно, чтобы компонент TRvSystem выполнил свою работу. У разработчика нет необходимости вызывать какие-либо методы компонента, чтобы направить отчет на печать.
В его составе инкапсулированы объекты, обеспечивающие вывод отчета из компонента TRvProject в один из трех системных приемников:
type
TReportDest = (rdPreview, rdPrinter, rdFile);
property ReportDest: TReportDest;
которое может принимать одно из трех значений типа TReportDest.
Соответственно, для каждого типа системного приемника имеется свойство, позволяющее задать все его основные параметры.
Для вывода в файл это комплексное свойство
property SystemFiler: TSystemFiler;
Внутри него задается имя файла во вложенном свойстве
property FileName: string;
но при этом вложенное свойство
type
TStreamMode = (smMemory, smTempFile, smFile, sraUser); property StreamMode: TStreamMode;
должно иметь значение smFile.
При выводе отчета для предварительного просмотра используется экземпляр класса TSystemPreview, который доступен через свойство
property SystemPreview: TSystemPreview;
Его свойства совпадают со свойствами компонента TRvRenderPreview.
Стандартное диалоговое окно предварительного просмотра отчета Rave Reports представлено на Рисунок 23.3.
Компоненты Rave Reports и их назначение
Компоненты Rave Reports и их назначение
Компоненты для создания отчетов и управления расположены на странице Rave Палитры компонентов. Они делятся на следующие функциональные группы.
Компоненты Rave Reports в приложении Delphi
Рисунок 23.1. Компоненты Rave Reports в приложении Delphi

Для того чтобы приложение Delphi могло выполнять функции печати отчетов, разработчик должен выполнить следующий набор операций.
1. При помощи визуальной среды разработки Rave Reports необходимо создать проект отчета и сохранить его (см. гл. 24).
2. Перенести в проект приложения в Delphi компонент TRvProject и связать его с файлом проекта отчета (см. ниже) при помощи свойства ProjectFile.
3. Перенести в проект приложения в Delphi компонент TRvSystem и связать его с компонентом TRvProject. ля этого используется свойство Engine компонента TRvProject (см. ниже).
4. Написать код приложения, обеспечивающий просмотр и печать отчета (при необходимости и другие операции), используя методы компонента TRvProject (см. ниже).
Конечно же, это наиболее простой способ включения отчета в приложения. Для решения более сложных задач необходимо изучить использованные выше компоненты более детально.
Отчет в приложении Delphi
Отчет в приложении Delphi
Завершив обзор нового генератора отчетов, давайте обратимся к деталям программирования и посмотрим, что нужно сделать, чтобы приложение могло работать с отчетами.
Основой отчета является файл отчета с расширением rav. Он создается в визуальной среде разработки Rave Reports и может содержать произвольное число страниц. Каждая страница может быть оформлена графическими или текстовыми элементами или отображать данные из какой-либо базы данных. Другими словами, файл RAV — это проект будущего отчета, содержащий общую информацию об отчете, оформление его страниц и правила их заполнения.
После создания проект отчета необходимо связать с приложением Delphi. Для этого используется компонент TRvProject (Рисунок 23.1). Этот компонент обеспечивает представление отчета в приложении.
Но этого недостаточно, чтобы просмотреть или напечатать отчет. Для выполнения этих операций используется код ядра генератора отчета, который автоматически прикомпилируется к исполняемому коду приложения при переносе на любую форму проекта компонентов TRvProject и TRvSystem. Для управления операциями печати и просмотра в проекте должен присутствовать компонент TRvSystem (Рисунок 23.1).
Редактор свойства StoreRAV компонента TRvProject
Рисунок 23.2. Редактор свойства StoreRAV компонента TRvProject

Здесь можно задать файл проекта отчета. После этого в Инспекторе объектов в строке свойства storeRAv появятся дата и время загрузки проекта отчета. Это же время и дата будут сохранены в свойстве
property RaveBlobDateTime: TDateTime;
Отправить отчет на печать можно методом
procedure Execute;
или же методом
procedure ExecuteReport(ReportName: string);
который позволяет направить на печать отчет, заданный параметром ReportName. Он должен соответствовать имени отчета, хранящемуся в свойстве ReportName компонента TRvProject.
Отчет, содержащийся в компоненте Trvproject, может быть открыт для редактирования методом
procedure Open;
Не открывая отчет, вы не сможете использовать большинство свойств и методов компонента. Дело в том, что при открытии компонент загружает отчет из файла проекта или прикомпилированного кода (в случае использования свойства StoreRAV).
Сохранение и закрытие отчета соответственно выполняются методами
procedure Save; procedure Close;
Кроме этого, действия, аналогичные методам open и close, выполняются свойством
property Active: Boolean;
Если свойству присвоить значение True — отчет открывается, иначе — закрывается.
До и после открытия и закрытия отчета вызывается четверка методов-обработчиков:
property aeforeOpen: TNotifyEvent;
property AfterOpen: TNotifyEvent;
property BeforeClose: TNotifyEvent;
property AfterClose: TNotifyEvent;
Стандартное диалоговое окно предварительного просмотра компонента TRvSystem
Рисунок 23.3. Стандартное диалоговое окно предварительного просмотра компонента TRvSystem

Заголовок этого окна задается свойством
property TitlePreview: TFormatString;
Перед открытием окна предварительного просмотра вызывается метод-обработчик
property OnPreviewShow: TNotifyEvent;
За вывод отчета на печать отвечает инкапсулированный в компоненте объект типа TSystemPrinter. К нему можно обратиться при помощи свойства
property SystemPrinter: TSystemPrinter;
Его свойства совпадают со свойствами компонента TRvRenderPrinter.
Перед тем как отправить отчет одному из трех системных приемников, компонент открывает диалог настройки печати (Рисунок 23.4).
Компоненты Rave Reports и отчеты в приложении Delphi
Безопасность доступа к данным
Безопасность доступа к данным
На третьем этапе созданное соединение и просмотр можно "защитить". Для этого из списка в диалоговом окне Data Connections (см. Рисунок 24.4) выбирается объект аутентификации Data Lookup Security Controller. Он сразу же появляется в словаре просмотра данных Data View Dictionary. Остается только подключить его к нужному просмотру. Для этого используется его свойство Dataview, в котором необходимо задать требуемый объект просмотра. Списки имен пользователей и их пароли задаются свойствами UserField и PasswordField соответственно.
Объект аутентификации можно подключить и для использования в отдельном отчете. Для этого применяется свойство securityControl отчета, в котором указывается нужный объект.
Еще один объект — простой объект аутентификации (объект Simple Security Controller в диалоговом окне Data Connections) — в свойстве userList содержит список имен и паролей в формате UserName=Password. Он может использоваться только в отчетах.
Библиотека отчетов
Библиотека отчетов
В первую очередь это отчеты, входящие в состав библиотеки отчетов. Каждый из этих отчетов описывает отдельный, самостоятельный отчет. Любой из них может быть загружен в компонент TRvproject для использования в приложениях Delphi. Первый отчет в списке по умолчанию становится текущим. Для смены текущего отчета достаточно дважды щелкнуть на нем в дереве проекта и это состояние будет сохранено при закрытии проекта.
Для того чтобы добавить к проекту новый отчет, можно использовать кнопку на главной панели окна визуальной среды или команду File | New Report главного меню. Для удаления отчета достаточно сделать его текущим и нажать клавишу
Для каждого отчета необходимо заполнить свойства Name и FullName, которые используются для идентификации отчета при работе с ним в Delphi (см. гл. 23). Кроме этого, в свойстве Description полезно заполнить описание отчета и задать единицы измерения, т. к. по умолчанию установлено использование дюймов.
Отчет может содержать произвольное число страниц. Напечатать можно как все страницы, так и их произвольное подмножество. Для разработчика список страниц отчета, которые предлагаются к печати по умолчанию, доступен в свойстве PageList. С этим свойством связан редактор страниц Page List Editor (Рисунок 24.3), который позволяет выбирать страницы отчета и формировать из них список для печати. При этом одна страница может быть включена в список несколько раз.
Для добавления к текущему отчету новой страницы используйте кнопку на главной панели окна визуальной среды или команду File | New Report Page главного меню. Для удаления выберите страницу и нажмите клавишу
Страница имеет имя, задаваемое свойством Name, а также несколько свойств, задающих ее важнейшие параметры: Orientation, PageSize, PageHeight, PageWidth.
Свойство GotoPage позволяет задать страницу, которая будет напечатана после этой. Порядок печати страниц по умолчанию соответствует их порядку в дереве отчета.
Дерево проекта отчета
Рисунок 24.2. Дерево проекта отчета

Диалог Query Advanced Designer, позволяющий настроить объект просмотра данных
Рисунок 24.5. Диалог Query Advanced Designer, позволяющий настроить объект просмотра данных

В этом диалоговом окне нужно выбрать из списка доступных таблиц источника данных необходимые и задать требуемые соотношения между полями. Страница Layout двухстраничного диалога позволяет выбирать таблицы. Нужная таблица выбирается из списка Tables в правой части и перетаскивается в левую часть, где каждая таблица представляется в виде списка полей. Необходимые поля также можно выбирать для будущего использования. Перетаскиванием полей между таблицами можно задавать внешние ключи.
Страница Sorting & Gouping позволяет выбрать поля для сортировки и группировки. Здесь в списке слева представлены выбранные ранее таблицы и поля. Поля можно переносить в список Sort Fields справа. Поля в этом списке будут использованы для сортировки наборов данных. А из полей для сортировки можно выбрать поле для группировки. Для этого используется список Group By.
При нажатии на кнопку Editor появляется редактор с текстом запроса SQL, реализующий все сделанные ранее настройки. При необходимости его можно исправить вручную.
После завершения настройки просмотра новый объект просмотра появляется в словаре просмотра данных Data View Dictionary и может использоваться в отчетах.
Созданный запрос просмотра доступен через его свойство Query.
Диалог выбора типа объекта доступа к данным Data Connections
Рисунок 24.4. Диалог выбора типа объекта доступа к данным Data Connections

Графические элементы управления
Графические элементы управления
Графические элементы оформления расположены на странице Drawing Палитры инструментов. Конечно, с их помощью вам не удастся изобразить картину Сальвадора Дали, но для оформления отчетов и рисования таблиц они вполне подойдут.
Это основные три элемента оформления для рисования линий:
Элементы оформления square и Rectangle изображают квадрат и прямоугольник соответственно.
Элементы Ellipse и circle изображают эллипс и круг.
Инструментарий визуальной среды создания отчетов
Инструментарий визуальной среды создания отчетов
Пользовательский интерфейс визуальной среды создания отчетов Rave Reports во многом напоминает среду разработки Delphi (Рисунок 24.1). В верхней части окна располагается панель инструментов, состоящая из набора кнопок слева и Палитры инструментов справа. В Палитре инструментов располагаются не только элементы оформления отчетов, но и инструменты для их настройки и управления.
Давайте посмотрим, для чего предназначены закладки Палитры инструментов. Первые четыре содержат элементы оформления отчетов:
Элементы Band, DataBand и Region на странице отчета
Рисунок 24.7. Элементы Band, DataBand и Region на странице отчета

Другие свойства полос и способы создания простых и сложных отчетов рассматриваются в гл. 26.
Элементы для представления текста и изображений
Элементы для представления текста и изображений
На странице Standard Палитры инструментов расположены элементы оформления, предназначенные для отображения текста и изображений. Рассмотрим их.
Для представления однострочного текста имеется простой элемент оформления Text. Текст задается свойством Text. Другие стандартные свойства позволяют настраивать шрифт, цвет и т. д. Кроме этого, свойство Rotation позволяет повернуть текст на любой угол в диапазоне от 0 до 360 градусов.
Для представления многострочного текста используется элемент оформления Memo. Текст задается свойством Memo.
Если вам необходимо объединить несколько элементов оформления в группу (например несколько строк текста и изображений в заголовке страницы) и использовать их на странице совместно, применяется невизуальный элемент оформления Section. Размещенные на нем другие элементы как бы оказываются на самостоятельной странице, ограниченной элементом section. Вы можете выделять и перемещать отдельные элементы, но делать это только внутри секции. А при перемещении секции по странице все расположенные на ней элементы также перемещаются вместе с ней. Выравнивание элементов тоже работает по границам секции.
Для представления изображений используется элемент оформления Bitmap. Он позволяет оформлять отчеты изображениями, сохраненными в файлах в формате BMP. Для загрузки изображения используется диалог, открывающийся при щелчке на кнопке свойства image. Также при помощи свойства FileLink можно связать элемент с файлом изображения и при печати отчета оно будет загружено. Кроме этого, изображение можно загрузить из базы данных. Для этого используются свойства Dataview (определяет объект просмотра данных) и DataField (задает поле изображения).
Изображение можно масштабировать. Для этого используется свойство Matchside. При его значениях msHeight, mswidth, msBoth изображение соответственно масштабируется по горизонтали, вертикали или в двух измерениях. Естественно, при этом может произойти искажение изображения. А вот при значении msinside изображение будет масштабировано пропорционально.
Аналогичными свойствами обладает элемент оформления MetaFile. Но изображение в формате WMF загружается в него при помощи свойства FileLink или DataField.
Невизуальный элемент оформления FontMaster позволяет задать единые свойства шрифта для группы элементов оформления. Его свойство Font определяет шрифт. И этот шрифт будет использоваться всеми элементами, в чьих свойствах FontMirror будет указан данный элемент FontMaster.
Невизуальный элемент оформления pageNuminit обеспечивает нумерацию страниц, начиная с той, на которой он расположен. Свойство initvalue задает номер, с которого начинается отсчет нумерации. А при помощи свойств initoataview и initoataFieid можно загрузить это значение из базы данных.
Элементы отображения данных
Элементы отображения данных
Элементы отображения данных представляют собой модифицированные стандартные элементы, размещаются на структурных элементах отчета и отображают данные из связанных с ними полей просмотра данных. Они расположены на странице Report Палитры инструментов.
Все перечисленные элементы (в том числе и элемент Bitmap) связываются с просмотром данных и полем одинаково.
Каталог глобальных страниц
Каталог глобальных страниц
Каталог глобальных страниц объединяет страницы, доступные из любого отчета библиотеки отчетов. Таким образом вы можете оформить все отчеты проекта одинаково. Например, для всех отчетов можно создать глобальные страницы титульного листа, общего заголовка и т. д.
Добавить новую страницу можно при помощи команды главного меню File | New Global Page. После этого страница появляется в списке каталога и доступна для редактирования.
Глобальная страница добавляется в отчет при помощи редактора страниц (см. Рисунок 24.3). Для этого необходимо выбрать нужную страницу из списка Global Pages и нажать на кнопку Add Global.
Обработка событий
Обработка событий
Каждому отчету, странице или элементу оформления можно назначить один или несколько методов-обработчиков событий. Для этого используется Редактор событий Event Editor, доступный через одноименную закладку в центральной части окна визуальной среды Rave Reports.
Для текущего элемента здесь можно выбрать из списка Avaiable Events одно из доступных для обработки событий. При этом будет создан обработчик события, программный код которого вводится в окне редактора внизу. В списке Defined Events отображаются события, обрабатываемые текущим элементом.
Синтаксис кода, используемый для обработки событий, аналогичен синтаксису Object Pascal. При этом можно применять некоторые процедуры и функции Delphi.
Например, в метод-обработчик события BeforePrint элемента оформления Texti можно поместить следующий код:
Textl.Text := IntToStr(StrToInt(Text1.Text) + 1);
который обеспечит нумерацию страниц отчета.
Проверка созданного кода выполняется при нажатии кнопки Compile.
Отображение данных в отчетах
Отображение данных в отчетах
Для представления данных в отчетах предназначены специализированные элементы оформления, представленные на странице Report Палитры инструментов.
Они делятся на две функциональные группы.
В первую группу выделены элементы, обеспечивающие размножение строк в отчете, их заполнение данными, а также группировку при создании сложных отчетов. Эти элементы мы будем называть структурными.
Во второй группе объединены элементы оформления, созданные на основе стандартных и обеспечивающие отображения текущего значения конкретного поля таблицы просмотра.
Проект отчета
Проект отчета
Визуальная среда работает с проектом отчета, который создается или загружается из файла с расширением rav. Состав проекта отчета отображается в дереве проекта отчета в панели в правой части окна визуальной среды (Рисунок 24.2).
Корневой элемент RaveProject содержит три дочерние ветви:
Редактор полос отчета Band Style Editor
Рисунок 24.6. Редактор полос отчета Band Style Editor

Группа флажков Print Location в правой части диалогового окна определяет назначение полосы. А группа Print Occurrence задает, в каком месте отчета появляется полоса:
Примечание
Для каждого из перечисленных выше типов в скобках указан символ, который используется для обозначения типа полосы на странице отчета в визуальной среде Rave Reports (Рисунок 24.7). Таким образом, по совокупности символов разработчик может оценить роль той или иной полосы в отчете, не обращаясь к редактору.
Редактор страниц отчета Page List Editor
Рисунок 24.3. Редактор страниц отчета Page List Editor

Свойство GridLines позволяет задать плотность измерительной сетки, накладываемой на страницу в визуальной среде для удобства размещения элементов оформления.
Резюме
Визуальная среда Rave Reports позволяет создавать проекты отчетов. Каждый такой проект может содержать несколько отчетов.
Инструментарий визуальной среды позволяет конструировать страницы отчетов из элементов оформления, настраивать их свойства и создавать обработчики событий.
При помощи набора объектов доступа к данным к отчету можно подключить внешний источник данных. При этом можно использовать одну из трех технологий доступа к данным: ADO, dbExpress, BDE. Специализированный редактор предназначен для визуального построения просмотра данных, а набор элементов отображения данных, используя поля созданного просмотра, обеспечивает создание отчетов.
Штрихкоды
Штрихкоды
На странице Ваг Code разработчику доступны шесть элементов оформления, позволяющие включать в отчеты штрихкоды. Все они реализуют различные стандарты, но значение для кодирования у всех задается одним свойством Text. Элементы PostNetBarCode, I2of5BarCode, UPCBarCode и EANBarCode позволяют вводить только числа, а элементы Code39BarCode и Godei28BarCode могут работать и с буквенно-цифровыми последовательностями.
При необходимости можно рассчитать и напечатать контрольную сумму. Свойство usechecksum при значении True рассчитывает ее, а свойство Printchecksum, будучи установленным в значение True, печатает.
Штрихкод можно развернуть, но только с дискретностью 90°. Для этого используется свойство BarCodeRotation.
Словарь просмотров данных
Словарь просмотров данных
Словарь просмотра данных объединяет разнообразные объекты доступа к данным. Для создания нового объекта необходимо воспользоваться командой главного меню File | New Data Object. После этого открывается диалог выбора типа объекта Data Connections (Рисунок 24.4).
Здесь доступны следующие типы объектов:
Соединение с источником данных и просмотры
Соединение с источником данных и просмотры
В первую очередь необходимо создать соединение с источником данных. Для этого в диалоге (см. Рисунок 24.4) необходимо выбрать объект соединения Database Connection и, после нажатия кнопки Next, в следующем окне выбрать одну из трех возможных технологий доступа к данным: ADO, dbExpress, BDE. В зависимости от сделанного выбора настраиваются параметры соединения. Подробнее о технологиях доступа к данным рассказывается в главах части IV.
После завершения настройки готовое соединение появляется в списке Data View Dictionary.
На втором этапе к работающему соединению можно подключать объекты просмотра данных. В диалоге Data Connections (см. Рисунок 24.4) надо выбрать тип объекта доступа к данным Driver Data View и нажать на кнопку Next. Затем в следующем окне нужно выбрать одно из существующих соединений (см. выше). После этого появляется диалог Query Advanced Designer (Рисунок 24.5).
Стандартные элементы оформления и их свойства
Стандартные элементы оформления и их свойства
Теперь остановимся подробнее на элементах оформления отчетов. Они используются так же, как и компоненты в Delphi. Выбранный элемент переносится из Палитры инструментов на страницу отчета. Здесь его можно разместить в нужном месте, изменить его размеры и настроить свойства. Свойства элемента оформления доступны на панели слева. Кроме этого, наиболее важные визуальные свойства (цвет, стиль линий и заполнения и т. д.) вынесены на Палитру инструментов.
После переноса на страницу имя элемента оформления также появляется в дереве проекта.
Структурные элементы отчета
Структурные элементы отчета
Рассмотрим структурные элементы.
Основой отчета, использующего просмотры баз данных, является элемент Region. Он создает в отчете область, предназначенную для размещения любых других элементов и определяющую часть страницы отчета, отведенную под отображение данных. Он обладает одним интересным свойством Columns, которое задает число колонок, в которых будет печататься отчет.
При создании отчета, использующего базу данных, этот элемент переносится на страницу в первую очередь. Затем приходит очередь элементов Band и DataBand.
Элемент Band создает полосу, на которой можно располагать стандартные элементы оформления. Он служит для оформления заголовков, сносок, врезок и других статичных фрагментов оформления отчетов, которые не изменяются при печати просмотра данных.
Элемент DataBand создает полосу, моделирующую строку просмотра данных. На ней располагаются элементы отображения данных, которые будут рассмотрены ниже. При печати отчета для каждой строки печатается новый экземпляр полосы элемента DataBand со всеми расположенными на ней элементами оформления. Таким образом и получается отчет, отображающий строка за строкой весь просмотр данных.
Важнейшее свойство Bandstyle определяет роль и поведение полосы в отчете. С ним связано диалоговое окно Band Style Editor (Рисунок 24.6), которое отображает взаимосвязь полос в области Region отчета и позволяет задать поведение текущей полосы.
В левой части диалога отображается список всех полос отчета с их взаимосвязями (отношениями "один-ко-многим", группировкой, вложенностью и т. д.), текущая полоса выделяется жирным шрифтом с подчеркиванием. Имя каждой полосы отображается трижды. И это не ошибка разработчиков, а желание показать, что каждая полоса размножается для печати записей просмотра данных.
Внешние источники данных в отчете
Внешние источники данных в отчете
Все объекты, обеспечивающие доступ к внешним источникам данных из отчетов проекта, собраны в словаре просмотра данных Data View Dictionary. Новый объект создается командой File | New Data Object главного меню.
Закладка Page Designer визуальной среды создания отчетов Rave Reports
Рисунок 24.1. Закладка Page Designer визуальной среды создания отчетов Rave Reports

Закладка Page Designer содержит еще один блокнот, каждая из страниц которого соответствует одной странице отчета. Когда вы добавляете к отчету новую страницу, здесь появляется еще одна закладка с именем новой страницы. На страницы можно переносить элементы оформления, изменять их размеры и местоположение. На страницу также можно спроектировать измерительную сетку, которая поможет размещать и выравнивать элементы оформления. Обрамляют страницу вертикальная и горизонтальная линейки.
На страницу можно переносить элементы оформления из Палитры инструментов, и затем элементы оформления можно выделять, настраивать их свойства, перемещать и удалять.
Закладка Event Editor обеспечивает создание методов-обработчиков событий для отчетов, страниц, элементов оформления и т. д.
Правую часть окна среды разработки занимает панель проекта отчета. Дерево проекта содержит все его составные части. При двойном щелчке на элементе дерева он отображается на странице в центральной части.
В левой части окна среды разработки располагается аналог Инспектора объектов Delphi, в котором доступны свойства текущего элемента. В нижней части этой панели отображается подсказка для текущего свойства.
Компоненты Rave Reports и отчеты в приложении Delphi
Дерево проекта SimpleDemo для отчета rptXP
Рисунок 25.2. Дерево проекта SimpleDemo для отчета rptXP

Добавление страниц к отчету
Добавление страниц к отчету
После создания первой страницы к отчету необходимо добавить еще две страницы и оформить их по образцу первой. Здесь все операции рутинны и не требуют дополнительных пояснений. В результате дерево проекта для отчета rptxp выглядит так, как показано на Рисунок 25.2.
Кроме обычных страниц, принадлежащих отчету, в Rave Reports можно создавать глобальные страницы, которые можно связать с любым отчетом проекта (например, титульные страницы). Для создания новой глобальной страницы используется команда File | New Global Page главного меню. Затем процесс разработки не отличается от обычных страниц.
Теперь для того, чтобы при просмотре или печати отчета отображались все страницы, а не только первая, необходимо дополнительно настроить свойство PageList отчета. В редакторе свойства необходимо перенести в список Page List все нужные страницы. Для этого страница выбирается из выпадающего списка Report Pages. Затем нужно щелкнуть на кнопке Add Page. Аналогичная операция выполняется и для глобальных страниц проекта, доступных в списке Global Pages.
Результат для отчета rptxp представлен на Рисунок 25.3.
Использование элемента FontMaster
Использование элемента FontMaster
Невизуальный элемент FontMaster позволяет использовать одинаковые шрифты в элементах оформления, например, в пределах одной секции. Для этого необходимо перенести в секцию элемент FontMaster и задать в его свойстве Font нужные характеристики шрифта. После этого во всех элементах, которые будут использовать этот шрифт, в списке свойства FontMirror надо выбрать этот элемент FontMaster.
В результате, один раз настроив шрифт, можно применить его для любого числа элементов оформления.
Этапы создания отчета и включение его в приложение
Этапы создания отчета и включение его в приложение
Процесс создания отчета с использованием генератора отчетов Rave Reports состоит из трех этапов. Первый выполняется в визуальной среде Rave Reports, второй и третий — в среде разработки Delphi.
1. На первом этапе в визуальной среде Rave Reports создается проект отчета и в нем необходимые страницы, объекты доступа к данным (см. гл. 24). На страницах располагаются элементы оформления, при необходимости к ним подключаются просмотры данных (если отчет отображает информацию из таблиц базы данных) и объекты аутентификации пользователей отчетов. Создаются обработчики событий. И в завершение этапа готовый проект отчета сохраняется в файле с расширением rav.
2. На втором этапе в проект приложения в Delphi переносятся компоненты TRvProject и TRvSystem (см. г/?. 23) со страницы Rave Палитры компонентов. При этом в состав приложения автоматически включается ядро генератора отчета. Первый компонент связывается с файлом проекта отчета и представляет в приложении отчет со всеми его свойствами, страницами, элементами оформления и т. д. Второй компонент связывается с первым, взаимодействует с ядром генератора отчетов и обеспечивает печать отчета из приложения.
3. На третьем этапе создается программный код, обеспечивающий выполнение функций приложения, связанных с отчетом. Наряду со стандартными операциями предварительного просмотра и печати отчета это могут быть загрузка из файла и сохранение в файле, преобразование формата данных отчета, изменение содержания отчета в зависимости от выполненных пользователем действий и т. д. На этом этапе используются свойства, методы и методы-обработчики событий компонентов TRvProject и TRvSystem и других компонентов со страницы Rave Палитры компонентов.
Далее в этой главе мы подробно рассмотрим перечисленные этапы и различные аспекты программирования, связанные с ними.
Компонент TRvNDRWriter
Компонент TRvNDRWriter
Компонент TRvNDRWriter предназначен для сохранения отчетов в файлах. При этом используется двоичный формат NDR.
Приемник данных определяется свойством
type
TStreamMode = (smMemory, smTempFile, smFile, smUser); property StreamMode: TStreamMode;
smMemory — для вывода данных используется поток в памяти (объект типа TMemoryStream);
smTempFile — данные сохраняются во временном файле, в папке, определенной в операционной системе для хранения временных файлов;
smFile — данные сохраняются в файле;
smUser — данные передаются в поток, заданный разработчиком.
Имя файла, в котором будет сохранен отчет, определяется свойством
property FileName: String;
А для определения потока используется свойство
property Stream: TStream;
Таким образом, если вы хотите использовать для сохранения отчета файл, перед использованием компонент настраивается, например, так:
RvNDRWriterl.StreamMode := smFile; RvNDRWriterl.FileName := ReportFilePath;
Если вы планируете использовать поток, сделайте следующим образом:
var ReportStreara: TMemoryStreain;
ReportStream := TMemoryStream.Create/try
RvNDRWriterl.StreamMode := smUser;
RvNDRWriterl.Stream := ReportStream;
finally
ReportStream.Free; end;
Но сначала этот отчет необходимо создать. Для этого используется обширный набор методов, позволяющих отображать текст и графику, создавать таблицы и заполнять их данными. Перед началом работы следует вызвать метод
procedure Start;
а по окончании создания отчета использовать метод
procedure Finish;
Например, следующий фрагмент кода создает в отчете текст с заданным положением:
with RvNDRWriterl do
begin
Start;
Units := unMM;
SetFont('Times New Roman', 14);
Bold := True;
OriginX := 0.0;
OriginY := 0.0;
GotoXY(1.0, 12.0);
Print('Заголовок ttl');
GotoXY(6.0, 18.0);
Println('Заголовок 12');
GotoXY(6.0, 24.0);
Println('Заголовок 13');
GotoXY(6.0, 30.0);
PrintIn('Заголовок #4');
Finish;
end;
А вот так можно нарисовать прямоугольник и разместить в нем изображение:
with RvNDRWriterl do
begin
Start;
Units := unMM;
SetBrush(clBlue, bsSolid, nil);
Rectangle (5.0, 35.0, 65.0, 95.0);
Bitmap := TBitmap.Create;
Bitmap.LoadFromFile('factory.BMP');
PrintBitmapRect(10.0, 40.0, 60.0, 90.0, Bitmap);
Bitmap.Free;
Finish;
end;
В данном фрагменте кода метод
procedure PrintBitmapRect(XI,Y1,X2,Y2: double;
Bitmap: TBitmap);
отображает растровое изображение Bitmap в прямоугольнике, обеспечивая его масштабирование в соответствии с размерами прямоугольника.
Внимание
Компонент TRvNDRWriter имеет свойство canvas (см. гл. 10), но использовать его нельзя — любые операции с канвой не возымеют действия и ваши труды не будут сохранены.
При использовании пары методов start и Finish не нужно предпринимать никаких дополнительных усилий для сохранения отчета — это будет сделано Методом Finish.
Множество других свойств и методов компонента (мы не будем останавливаться на них специально, т. к. их использование достаточно прозрачно) обеспечивают оформление отчета, управление страницами, настройку принтера и т. д.
Нумерация страниц отчета
Нумерация страниц отчета
В первую очередь создадим заголовок страницы и включим механизм нумерации страниц. Для этого перенесем на страницу элемент Section со страницы Standard Палитры инструментов и поместим его в верхней части страницы отчета. Эта секция будет объединять элементы оформления заголовка.
Поместим в секции элемент DataText — нам необходимо его свойство DataFieid. Редактор свойства Data Text Editor (Рисунок 25.1) позволяет настраивать свойство так, чтобы элемент мог отображать разнообразные данные. Сейчас нас интересуют номера страниц отчета.
Из списка Report Variables, который содержит глобальные переменные отчета, необходимо выбрать переменную RelativePage. Затем нужно щелкнуть на кнопке Insert Report Var и переменная Report.RelativePage появится в поле Data Text в нижней части диалога. Эта переменная при печати отчета будет содержать порядковый номер текущей страницы.
Отчет в приложении
Отчет в приложении
Теперь, когда проект SimpleDemo.RAV с двумя отчетами готов, перейдем к разработке приложения в Delphi.
Любое приложение, использующее генератор отчетов Rave Reports, должно иметь как минимум пару компонентов — TRvProject и TRvSystem. Первый из
них есть проект отчета в приложении. С его помощью разработчик получает доступ к отчетам проекта и их свойствам. Второй компонент обеспечивает использование ядра генератора отчетов Rave Reports при печати или предварительном просмотре отчета (см. Рисунок 23.1). Подробнее о свойствах и методах этих компонентов см. гл. 23.
При использовании этих компонентов в состав исполняемого кода приложения автоматически включается ядро генератора отчетов Rave Reports. Соответственно при распространении приложения не требуются дополнительные файлы — даже файл проекта отчета можно включить в приложение (см. ниже).
Компонент TRvProject необходимо связать с файлом проекта Simple-Demo. RAV. Для этого используется его свойство
property ProjectFile: string;
Файл RAV можно распространять вместе с приложением или включить его в состав исполняемого файла. Для этого используется свойство
property StoreRAV: boolean;
компонента (см. Рисунок 23.2).
Перед использованием отчетов из компонента TRvProject его необходимо открыть. В нашем примере при открытии формы приложения компонент открывается и в список считываются полные имена отчетов проекта и отображается описание текущего отчета:
procedure TfmMain.FormShow(Sender: TObject);
begin
rpProject.Open;
rpProject.GetReportList(IbxRptLiat.Items, True);
rpProject.ReportDescToMemo(meDesc) ;
end;
Но только один из отчетов доступен для использования одновременно. Для смены текущего отчета можно воспользоваться методом
function SelectReport(ReportName: string;
FullName: boolean): boolean;
Для идентификации текущего отчета компонент TRvProject имеет два свойства, которые возвращают его имя и полное имя. Это соответственно свойства ReportName и ReportFullName. При необходимости использовать имя отчета для одного из методов (например метод SelectReport) можно использовать оба имени. Во всех методах, где в качестве параметра применяется имя отчета, имеется дополнительный параметр FullName типа Boolean. При его значении True используется полное имя отчета.
Обратите внимание, что перед использованием любых свойств и методов, относящихся к отчету в компоненте TRvProject, отчет необходимо открыть.
Для этого используется метод open:
RvProjectl.Open;
или свойство
Active: RvProjectl.Active := True;
Так же, если в процессе работы приложения в компонент TRvproject нужно загрузить новый проект отчета, процедуру открытия нужно повторить:
RvProjectl.Close;
RvProjectl.SetProjectFile
(dlgOpenProject.FileName);
RvProjectl.Open;
Компонент отчета необходимо связать с компонентом TRvSystem. Для этого в свойстве Engine компонента TRvproject необходимо задать ссылку на компонент TRvSystem.
Преобразование форматов данных
Преобразование форматов данных
С компонентом TRvNDRWriter (вернее с файлами в формате NDR, которые он создает) взаимодействует ряд компонентов Rave Reports, которые обеспечивают преобразование данных из этого специфического формата в более распространенные форматы.
Сначала необходимо загрузить отчет из файла NDR в поток.
После этого вызывается метод
procedure PrintRender(NDRStream: TStream; OutputFileName: TFileName);
который и выполняет преобразование:
var ReportStream: TMemoryStream;
ReportStream := TMemoryStream.Create;
try
ReportStream.LoadFromFile(NDRFilePath);
RvRenderHTMLl.NDRStream := ReportStream;
RvRenderHTMLl.PrintRender(ReportStream, 'sdf);
finally
ReportStream.Free;
end;
Просмотр и печать отчета
Просмотр и печать отчета
Если в предыдущем пункте, обсуждая отчет в приложении Delphi, мы говорили о компоненте TRvproject, то за выполнение любых операций с ним отвечает компонент TRvSystem.
При стандартной настройке этого компонента при печати или предварительном просмотре отчета всегда отображается диалог настройки печати (см. Рисунок 23.4). Если отображение этого диалога необходимо, печать текущего отчета компонента TRvproject, с которым связан данный компонент TrvSystem. осуществляется методом
procedure Execute;
любого из этих компонентов.
Если диалог настройки печати не нужен, компонент TRvSystem позволяет выполнить операцию напрямую. Для этого необходимо выполнить несколько действий.
Сначала нужно настроить свойство
type
TReportDest = (rdPreview, rdPrinter, rdFile);
property DefaultDest: TReportDest;
которое определяет, куда будет направлен отчет — в окно просмотра, на принтер или в файл.
Затем необходимо изменить свойство
type
SystemSetup = (ssAllowSetup, ssAllowCopies, ssAllowCollate,
ssAllowDuplex, ssAllowDestPreview, ssAllowDestPrinter, ssAllowDestFiie,
ssAllowPrinterSetup);
TSystemSetups = Set of TSystemSetup;
property SystemSetups: TSystemSetups;
убрав из него опцию ssAllowSetup, которая включена по умолчанию:
RvSysteml.SystemSetups := RvSysteml.SystemSetups — [ssAllowSetup];
и, наконец ,свойству
property DoNativeOutput: Boolean;
необходимо присвоить значение False, т. к. по умолчанию оно имеет значение True, которое и заставляет компонент показывать диалог настройки печати перед выполнением операции.
Обратите внимание на очень важную деталь — чтобы все сделанные настройки действительно сработали, печать отчета необходимо выполнять только методом Execute Компонента TRvProjeet.
Простой отчет в визуальной среде Rave Reports
Простой отчет в визуальной среде Rave Reports
При описании первого этапа наша задача — описать возможности проекта RAV, исследовать его структуру и составные части, которые можно использовать в приложении Delphi. Собственно элементы оформления просты в использовании и мы акцентируем внимание лишь на нескольких элементах, требующих небольших пояснений.
Для начала создадим в визуальной среде Rave Reports новый проект (команда File | New главного меню). Обратите внимание, что по умолчанию вместе с проектом создается первый отчет Reportl с одной страницей Pagel. Его мы и используем, переименовав в rptxp. Проекту присвоим имя simpleDemo.
Редактор свойства DataField элемента DataText
Рисунок 25.1. Редактор свойства DataField элемента DataText

Аналогичным образом добавим переменную TotalPages, которая возвращает общее число страниц отчета. Затем вручную отредактируем текст в поле Data Text:
'Страница ' + Report.RelativePage + ' из ' + Report.TotaiPages
Шаблон номера страницы готов. Но для того, чтобы механизм нумерации заработал, необходимо перенести на страницу невизуальный элемент pageNuminit. Он будет работать автоматически. Единственное, что нужно сделать, — это настроить свойство initvalue, в котором задается номер начальной страницы.
Примечание
Примечание
Обратите внимание, что элемент PageNuminit должен быть только один и располагаться на первой странице, которая должна содержать свой номер. Иначе, на каждой странице, на которой есть такие элементы, нумерация начнется сначала.
Редактор свойства PageList отчета rptXP
Рисунок 25.3. Редактор свойства PageList отчета rptXP

Используя кнопки слева в группе Page List, можно изменять порядок следования страниц при печати отчета или удалять страницы из списка.
Теперь отчет rptxp готов. Дополнительно к нему, с использованием тех же элементов и действий, в рассматриваемом нами примере создан еще один отчет rptRV. При этом по умолчанию текущим считается отчет, который при последнем сохранении проекта был текущим.
Проект отчета сохранен в файле SimpleDemo.RAV.
Приложение Delphi, которое реализует печать
Резюме
Приложение Delphi, которое реализует печать отчетов, должно иметь в своем составе компоненты TRvProject и TRvSystem. Первый обеспечивает представление проекта отчета из файла RAV в приложении. Второй взаимодействует с ядром генератора отчетов и управляет печатью и предварительным просмотром отчетов.
Набор компонентов Rave Reports позволяет преобразовать формат данных отчета в наиболее распространенные форматы данных: HTML, RTF, PDF, TXT.
Сохранение отчета во внешнем файле
Сохранение отчета во внешнем файле
При помощи методов компонента TRvSystem можно сохранить отчет для последующей печати в формате PRN или сохранить проект Rave Reports в формате RAV.
Для реализации первого варианта необходимо в качестве источника печати указать файл:
...
if dlgSavePRN.Execute then begin
rsSystem.DoNativeOutput := False;
rsSystem.DefaultDest := rdFile;
rsSystem.SystemSetups := rsSystem.SystemSetups — [ssAllowSetup];
rsSystem.OutputFileName := dlgSavePRN.FileName;
rpProj ect.Execute;
end;
...
Как видите, здесь мы воспользовались методикой прямой печати, описанной в предыдущем разделе, указав в качестве приемника файл с расширением ргп, выбранный в стандартном диалоге выбора файла.
Если же нужно сохранить проект отчета в файле с расширением rav, можно использовать метод SaveToFile компонента TRvproject:
if dlgSaveProject.Execute
then RvProjectl.SaveToFile(dlgSaveProject.FileName);
Также просто выполнить и обратную операцию — загрузить в компонент TRvProject проект отчета из файла, но при этом не забудьте закрыть текущий отчет:
RvProjectl.Close;
RvProjectl.LoadFromFile(dlgOpenProject.FileName);
RvProjectl.Open;
Аналогичную функцию выполняет метод
procedure SetProjectFile(Value: String);
компонента TRvProject.
Компоненты Rave Reports и отчеты в приложении Delphi
Аутентификация пользователя в отчете
Аутентификация пользователя в отчете
Два объекта 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. Диалог выбора драйвера соединения

В диалоге выбора объекта доступа к данным (см. Рисунок 24.4) используются два объекта просмотра. Но с соединением на основе драйвера Rave Reports умеет взаимодействовать только один из них — Driver Data View. Для него необходимо выбрать соединение из созданных ранее в этом проекте (Рисунок 26.3).
Группирующий отчет
Группирующий отчет
Отчеты, работающие с базами данных, часто должны отображать данные с различными уровнями группировки. Обычно группировка осуществляется в наборе данных, если он создается на основе запроса SQL с применением оператора GROUP BY. Но в самом наборе данных невозможно предусмотреть оформление групп записей, однако это можно сделать в отчете.
Для полосы данных, которая отображает данные из просмотра с группировкой, можно создать полосы группового заголовка и группового окончания. Для этого используются свойства GroupDataView и GroupKey. Первое должно указывать на объект группирующего просмотра, а второе задает поле или несколько полей, по которым осуществляется группировка. Применительно к оформлению отчета это означает, что при изменении значения группового ключа будут напечатаны полосы группового заголовка и окончания.
В качестве таких полос могут использоваться обычные полосы и полосы данных. Обычные полосы применяются, если группировка имеет один уровень вложенности (для каждого значения группового ключа существует одна или несколько сгруппированных записей). Полосы данных используются, если группировка имеет несколько уровней (внутри группы выделяется еще один групповой ключ и каждая запись в группе имеет еще несколько сгруппированных записей второго уровня).
Кроме этого, для полос группового заголовка необходимо в свойстве ControllerBand задать основную полосу данных и настроить свойство Bandstyle. Для группового заголовка в редакторе Band Style Editor в группе Print Location устанавливается флажок Group Header (G), а для полосы группового окончания — флажок Group Footer (g).
Обычно группирующие запросы SQL используют агрегатные функции для вычисления одного или нескольких величин по всей группе. Чаще всего это общая денежная сумма или общее количество. Такие величины удобно размещать в полосах группового окончания.
Использование вычисляемых значений
Использование вычисляемых значений
На странице Reports Палитры инструментов визуальной среды Rave Reports доступны несколько компонентов, которые позволяют применять агрегатные функции к значениям полей набора данных, переданного через соединение в отчет.
К агрегатным относятся следующие функции:
Рассмотрим все эти элементы оформления.
Компонент 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

Это могут быть как простые элементы 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

На этом настройка отношения "один-ко-многим" завершена. Однако скажем еще несколько слов об использовании обычных полос при оформлении такого рода отчетов. В нашем примере две дополнительные полосы 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

Затем параметр или переменная может быть использована для дальнейших вычислений или напечатана при помощи элемента 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

При соединении через компоненты в Delphi сначала необходимо создать объект просмотра Direct Data View, который реализует прямой доступ к набору данных на основе активного соединения в приложении Delphi. При этом соединение может быть создано на основе любой доступной в Delphi технологии доступа к данным. Это позволяет сделать набор компонентов Rave Reports на странице Rave Палитры компонентов Delphi. Это следующие технологии доступа к данным:
Соединения с источниками данных в приложении
Соединения с источниками данных в приложении
Теперь давайте посмотрим, как нужно использовать специализированные компоненты Rave Reports в приложениях Delphi для того, чтобы создать соединение отчета с источником данных. Их основная задача — передать в отчет связанный набор данных.
Для создания соединения можно использовать следующие компоненты:
Соединения с источниками данных в Rave Reports
Соединения с источниками данных в Rave Reports
Если отчет Rave Reports должен отображать данные из какого-либо источника данных, на этапе разработки в визуальной среде в проект отчета должны быть добавлены специальные объекты, обеспечивающие соединение с источником данных и формирование набора данных, который затем отображается в отчете.
В Rave Reports существуют два типа соединений с источниками данных (Рисунок 26.1):
При соединении через драйвер Rave Reports проект отчета на этапе разработки и ядро генератора отчетов на этапе выполнения используют драйверы, которые реализованы в виде файлов с расширением rvd. Именно наличие этих файлов предопределяет выбор технологий доступа к данным при создании объекта соединения в среде разработки (Рисунок 26.2). В стандартную поставку Rave Reports 5.0 входят драйверы для следующих технологий доступа к данным:
Соединения с источниками данных в визуальной среде Rave Reports
Соединения с источниками данных в визуальной среде Rave Reports
Любой отчет, работающий с базами данных, должен быть настроен соответствующим образом в визуальной среде создания отчетов Rave Reports. Независимо от типа соединения здесь должен быть создан хотя бы один объект доступа к данным.
Если вы хотите создать соединение через драйверы Rave Reports, вам потребуется объект соединения и связанный с ним объект просмотра на основе запроса SQL.
При необходимости использовать соединение на основе компонентов Delphi в визуальной среде вам потребуется создать объект прямого просмотра.
Рассмотрим подробнее действия, которые необходимо выполнить в визуальной среде Rave Reports для создания и настройки соединения.
Список доступных соединений из приложения Delphi
Рисунок 26.4. Список доступных соединений из приложения Delphi

После создания объект прямого просмотра (компонент DataView) доступен в дереве проекта и может быть использован. В его состав входят все поля набора данных, связанного в приложении Delphi с компонентом соединения. И их можно подключить к элементам оформления нужного отчета (см. разд. "Типы отчетов" ниже в данной главе).
Список выбора соединения для просмотра Driver Data View
Рисунок 26.3. Список выбора соединения для просмотра Driver Data View

После этого в редакторе 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

А теперь займемся созданием отношения "один-ко-многим".
В подчиненной полосе данных ordBand необходимо задать значения для четырех свойств.
Страница простого табличного отчета в визуальной среде Rave Reports
Рисунок 26.5. Страница простого табличного отчета в визуальной среде Rave Reports

Элемент оформления 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
|
Задает фиксированное значение, над которым производится вычислительная операция |
После выполнения основной операции результат может быть обработан еще один раз, если вы зададите математическую функцию в свойстве
ResultFunction.
Таким образом, при помощи элемента CalcOp разработчик может реализовывать довольно сложные вычисления.
Если задать в качестве двух источников данных:
Типы отчетов
Типы отчетов
Сейчас мы займемся вопросами разработки собственно отчетов. Схема использования элементов оформления, работающих с объектами доступа к данным, стандартна для любых типов отчетов. Поэтому сначала мы рассмотрим общую методику на примере простого отчета, а затем перейдем к более сложным отчетам.
Для всех рассматриваемых типов отчетов создано демонстрационное приложение 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
Главная форма проекта DemoDragDrop
Рисунок 27.1. Главная форма проекта DemoDragDrop

Листинг 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:
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.3. Плавающие панели инструментов без заголовка окна

Полное рассмотрение внутреннего устройства механизмов Drag-and-Dock потребовало бы расширения объема этой главы. Тем, кто хочет использовать их на все 100%, рекомендуем обратиться к свойствам useDockManager и DockManager. Последнее представляет собой СОМ-интерфейс, позволяющий расширить возможности дока, вплоть до записи его состояния в поток (класс TStream).
Плавающие панели инструментов
Рисунок 27.2. Плавающие панели инструментов

Естественно, для программиста предусмотрены возможности контроля за этим процессом. Каждый переносимый элемент управления имеет два события, возникающие в моменты начала и конца переноса:
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;
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
|
Задает число прокручиваемых линий |
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 заставляет кнопку реагировать на нажатие клавиши
Для передачи фокуса любому оконному элементу управления программными средствами можно использовать метод
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) предназначены три метода обработчика, реагирующие на прокрутку:
В 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, позволяющее им автоматически масштабироваться при изменении содержимого (скажем, надписи на кнопке).
Компоненты Rave Reports и отчеты в приложении Delphi
Диалог команды Parameters меню Run
Рисунок 28.1. Диалог команды Parameters меню Run

Диалог мастера библиотеки ресурсов для включения в проект дополнительных файлов
Рисунок 28.5. Диалог мастера библиотеки ресурсов для включения в проект дополнительных файлов

Рисунок 28.4. Диалог мастера библиотеки ресурсов со списком папок для ресурсов локализации проекта

Диалог мастера библиотеки ресурсов со списком форм, включаемых в проект
Рисунок 28.2. Диалог мастера библиотеки ресурсов со списком форм, включаемых в проект

После этого в третьем диалоге мастера необходимо выбрать один или несколько языков локализации ресурсов (Рисунок 28.3). От этого выбора языка зависит расширение откомпилированного файла библиотеки и алгоритм поведения базового проекта при загрузке.
Диалог мастера библиотеки ресурсов со списком доступных языков локализации проекта
Рисунок 28.3. Диалог мастера библиотеки ресурсов со списком доступных языков локализации проекта

Если запускаемое приложение или DLL находит в своей папке одноименный файл с расширением, которое соответствует одной из возможных локализаций и эта локализация применяется в системе, то приложение использует ресурсы из этой библиотеки вместо собственных. Поэтому при определении имени файла библиотеки ресурсов категорически не рекомендуется изменять предложенное мастером имя.
Кроме того, папку с файлом ресурсов можно задать дополнительно. Для этого на следующей странице мастера необходимо указать нужную папку для каждого языкового ресурса или принять предложенную по умолчанию (Рисунок 28.4).
Затем вы можете добавить к библиотеке ресурсов собственные файлы. Это могут быть ресурсы любого рода, используемые приложением. В окне мастера (Рисунок 28.5) необходимо выбрать эти файлы.
В последующих диалогах мастера задается способ создания или обновления для каждого языкового ресурса и запускается процесс создания ресурса. При первоначальном создании DLL ресурсы можно только создавать, впоследствии их можно полностью перезаписывать или изменять.
По завершении работы мастера для каждого выбранного языка создается новый проект библиотеки ресурсов. Результат работы мастера выводится в информационном окне (Рисунок 28.6).
Директива cdecl
Директива cdecl
Реализует вызовы в стиле языка С. Параметры в стек помещаются справа налево. Очистка стека осуществляется вызывающей процедурой. Такие вызовы обеспечивают обслуживание переменного числа параметров, но скорость обработки меньше, чем в вызовах при реализации директивы pascal.
Эта директива в основном применяется для обращения к динамическим библиотекам, использующим соглашения о вызовах в стиле языка С. Использование директивы cdecl для библиотек Delphi не вызовет ошибку компиляции, но переменное число параметров не обеспечит.
Директива pascal
Директива pascal
Реализует вызовы в стиле языка Pascal. За очистку стека отвечает вызываемая процедура. Параметры помещаются в стек слева направо. Этот способ вызова является очень быстрым, но не поддерживает переменное число параметров. Используется для обеспечения обратной совместимости.
Директива register
Директива register
Эта директива используется по умолчанию. Поэтому нет необходимости добавлять ключевое слов register после объявления функции. Вызов такого типа называется быстрым (fast call). В нем используются три расширенных регистра процессора, в которые помещаются переменные длиной не более 32-х разрядов и указатели. Остальные параметры помещаются в стек слева направо. После использования стек очищается вызываемой процедурой.
Директива safecall
Директива safecall
Параметры помещаются в стек справа налево. Очистка стека осуществляется вызываемой процедурой. Используется в СОМ и основанных на ней технологиях.
Директива stdcall
Директива stdcall
Параметры помещаются в стек слева направо. Очистка стека осуществляется вызываемой процедурой. Этот вызов обеспечивает обработку фиксированного числа параметров.
Инициализация и завершение работы 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;
Рассмотрим их.
Это хороший способ организовать в динамической библиотеке необходимую в каждом случае обработку. Как это сделать?
Во-первых, необходимо создать процедуру, подходящую для процедурного типа 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. Окно с информацией о результате создания ресурса

Каждый созданный проект необходимо откомпилировать и включить в состав дистрибутива базового приложения.
Проект 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 данные или объекты принадлежат вызывавшему потоку.
Функции динамических библиотек могут вызываться двумя способами — явным и неявным. Рассмотрим их.
Компоненты Rave Reports и отчеты в приложении Delphi
Диалоговое окно New Items с выбранным объектом типа "поток"
Рисунок 29.3. Диалоговое окно New Items с выбранным объектом типа "поток"

Диалоговое окно New Thread Object
Рисунок 29.4. Диалоговое окно New Thread Object

5. Когда появится диалоговое окно для именования объекта поток, введите TPiThread и нажмите клавишу
Delphi создаст новый модуль и поместит в него шаблон для нового потока.
6. Код, вносимый в метод Execute, вычисляет число я, используя сходимость бесконечного ряда Лейбница:
Pi = 4 - 4/3 + 4/5 - 4/7 + 4/9 -...
Разумеется, отображать новое значение после каждой итерации — это то же самое, что стрелять из пушки по воробьям. На отображение информации система потратит в десятки раз больше времени, чем на собственно вычисления. Поэтому мы ввели константу updatePeriod, которая регулирует периодичность отображения текущего значения.
Код метода Execute показан ниже:
const
// Лучше использовать нечетное число для того, чтобы избежать эффекта // мерцания UpdatePeriod = 1000001;
procedure TPiThread.Execute; var sign : Integer;
PiValue, PrevValue : Extended; i : Int64;
begin
{ Place thread code here } PiValue := 4; sign := -1; i := 0; repeat Inc(i);
PrevValue := PiValue;
PiValue := PiValue + sign * 4 / (2*i+l); sign := -sign;
if i mod UpdatePeriod = 0 then
begin
GlobalPi := PiValue; GlobalCounter := i; Synchronize(fmMain.UpdatePi);
end;
until Terminated or (Abs(PiValue - PrevValue)<1E-19); end;
7. Откройте меню File и выберите пункт Save As. Сохраните модуль с потоком как uPiThread.pas.
8. Отредактируйте главный файл модуля uMain.pas и добавьте модуль uPiThread к списку используемых модулей в секции интерфейса. Он должен выглядеть так:
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, StdCtrls, uPiThread;
9. В секции public формы TfmMain добавьте ссылку на создаваемую нить: PiThread : TPiThread;
10. Добавьте в модуль uMain две глобальные переменные
GlobalPi : Extended;
GlobalCounter : Int64;
и метод UpdatePi:
procedure TfmMain.UpdatePi;
begin
if Islconic(Application.Handle) then
Exit;
LaValue.Caption := FloatToStrF(GlobalPi, ffFixed, 18, 18);
lalterNum.Caption := IntToStr(GlobalCounter) + ' iterations';
end;
Этот метод, если вы обратили внимание, вызывается из потока посредством процедуры Synchronize. Он отображает текущее значение приближения к числу "пи" и количество итераций.
В случае, если главное окно приложения свернуто, отображение не производится; так что после его развертывания вам, возможно, придется подождать некоторое время для обновления.
11. Выполните двойной щелчок на свободном месте рабочей области формы, при этом создастся шаблон метода FormCreate. Здесь мы отобразим значение системной константы р±:
procedure TfmMain.FormCreate(Sender: TObject);
begin
laBuiltln.Caption := FloatToStrF(Pi, ffFixed, 18, 18); end;
12. Выберите на форме переключатель (его название cbcalcuiate) и назначьте событию Onclick код, создающий и уничтожающий вычислительный поток в зависимости от состояния переключателя:
procedure TfmMain.cbCalculateClick(Sender: TObject);
begin if cbCalculate.Checked then
begin
PiThread := TPiThread.Create(True);
PiThread.FreeOnTerminate := True;
PiThread.Priority := tpLower;
PiThread.Resume; end else begin
if Assigned(PiThread) then PiThread.Terminate;
end;
end;
Таким образом, многопоточное приложение готово к запуску. Если все пройдет нормально, вы увидите картинку, подобную той, которая приведена на Рисунок 29.5.
Фоновые процедуры, или способ обойтись без потоков
Фоновые процедуры, или способ обойтись без потоков
Здесь мы рассмотрим возможность для организации фоновых действий (job) внутри однопоточной программы с сохранением реакции этого потока на события от мыши и клавиатуры.
Еще не столь давно программисты пытались эмулировать потоки, запуская процедуры внутри цикла обработки сообщений Windows. Цикл обработки сообщений (или цикл ожидания) — это особый фрагмент кода в программе, управляемой событиями. Он исполняется тогда, когда программа находит в очереди события, которые нужно обработать; если таковых нет, программа может выполнить в это время "фоновую процедуру". Такой способ имитации потоков весьма сложен, т. к. вынуждает программиста, во-первых, сохранять состояние фоновой процедуры между ее вызовами, а во-вторых, определять момент, когда она вернет управление обработчику событий. Если такая процедура выполняется долго, то у пользователя может сложиться впечатление, что приложение перестало реагировать на внешние события. Использование потоков снимает проблему переключения контекста, теперь контекст (стек и регистры) сохраняет операционная система.
В Delphi возможность создать фоновую процедуру реализована через событие Onldle .объекта Application!
type TIdleEvent = procedure (Sender: TObject;
var Done: Boolean)
of object;
property Onldle: TIdleEvent;
Обработчик этого события вы можете написать, поместив на форму компонент TApplicationEvents со страницы Additional Палитры компонентов.
Чтобы сделать в фоновом режиме какую-то работу, следует разбить ее на кванты и выполнять по одному кванту каждый вызов Onldle — иначе приложение будет плохо реагировать на внешние воздействия.
Гонки
Гонки
Ситуация гонок возникает, когда два или более потока пытаются получить доступ к общему ресурсу и изменить его состояние. Рассмотрим следующий пример. Пусть Поток 1 получил доступ к ресурсу и изменил его в своих интересах; затем активизировался Поток 2 и модифицировал этот же ресурс до завершения Потока 1. Поток 1 полагает, что ресурс остался в том же состоянии, в каком был до переключения. В зависимости от того, когда именно был изменен ресурс, результаты могут варьироваться — иногда код будет выполняться нормально, иногда нет. Программисты не должны строить никаких гипотез относительно порядка исполнения потоков, т. к. планировщик ОС может запускать и останавливать их в любое время.
Inc(i) ;
if i = iSomething then DoSomething;
Здесь i — глобальная переменная, доступная из обоих потоков. Пусть два или более потоков исполняют этот код одновременно. Поток 1 инкрементировал значение переменной i и хочет проверить ее значение для выполнения тех или иных условий. Но тут активизируется другой поток, который еще увеличивает значение i. В результате первый поток "проскакивает" мимо условия, которое, казалось бы, должно было быть выполнено.
Возникновения как ситуаций гонок, так и тупиков можно избежать, если использовать приемы, обсуждаемые ниже.
Как избежать одновременного запуска двух копий одного приложения
Как избежать одновременного запуска двух копий одного приложения
Такая задача возникает очень часто. Многие, особенно начинающие, пользователи не вполне понимают, что между щелчком по значку приложения и его запуском может пройти несколько секунд, а то и десятков секунд. Они начинают щелкать по значку, запуская все новые копии. Между тем, при работе с базами данных и во многих других случаях иметь более одной копии не только не нужно, но и вредно.
Идея заключается в том, чтобы первая создаваемая копия приложения захватывала некий, ресурс, а все последующие при запуске пытались сделать то же самое и в случае неудачи завершались.
Пример такого ресурса — общий блок в файле, отображаемом в память. Поскольку этот ресурс имеет имя, можно сделать его уникальным именно для вашего приложения:
var UniqueMapping : THandle;
FirstWindow : THandle
; begin
UniqueMapping := CreateFileMapping($ffffffff,
nil, PAGE_READONLY, 0, 32,'MyMap');
if UniqueMapping = 0 then
begin
ShowMessage(SysErrorMessage(GetLastError));
Halt;
end
else if GetLastError = ERROR_ALREADY_EXISTS then
begin
FirstWindow := FindWindowEx(0, 0, TfmMain.ClassName, nil);
if FirstWindowoO then
SetForegroundWindow(FirstWindow};
Halt;
end;
// Нет других копий — продолжение Application.Initialize;
Примерно такие строки нужно вставить в начало текста проекта до создания форм. Блок совместно используемой памяти выделяется в системном страничном файле (об этом говорит первый параметр, равный -1, см. описание функции CreateFileMapping). Его имя — муМар. Если при создании блока будет получен код ошибки ERROR_ALREADY__EXISTS, это свидетельствует о наличии работающей копии приложения. В этом случае приложение переключает фокус на главную форму другого экземпляра и завершается; в противном случае процесс инициализации продолжается.
Класс TThread
Класс TThread
Delphi представляет программисту полный доступ к возможностям программирования интерфейса Win32. Для чего же тогда фирма Borland представила специальный класс для организации потоков? Вообще говоря, программист не обязан разбираться во всех тонкостях механизмов, предлагаемых операционной системой. Класс должен инкапсулировать и упрощать программный интерфейс; класс TThread — прекрасный пример предоставления разработчику простого доступа к программированию потоков. Сам API потоков, вообще говоря, не очень сложен, но предоставленные классом TThread возможности вообще замечательно просты. В двух словах, все, что вам необходимо сделать, — это перекрыть виртуальный метод Execute.
Другая отличительная черта класса TThread — это гарантия безопасной работы с библиотекой визуальных компонентов VCL. Без использования класса TThread во время вызовов VCL могут возникнуть ситуации, требующие специальной синхронизации (см. разд. "Проблемы при синхронизации потоков" далее в этой главе).
Нужно отдавать себе отчет, что с точки зрения операционной системы поток — это ее объект. При создании он получает дескриптор и отслеживается ОС. Объект класса TThread — это конструкция Delphi, соответствующая потоку ОС. Этот объект VCL создается до реального возникновения потока в системе и уничтожается после его исчезновения.
Изучение класса TThread начнем с метода Execute:
procedure Execute; virtual; abstract;
Это и есть код, исполняемый в создаваемом вами потоке TThread.
Примечание
Примечание
Хотя формальное описание Execute — метод abstract, но мастер создания нового объекта TThread создает для вас пустой шаблон этого метода.
Переопределяя метод Execute, мы можем тем самым закладывать в новый потоковый класс то, что будет выполняться при его запуске. Если поток был создан с аргументом CreateSuspended, равным False, то метод Execute выполняется немедленно, в противном случае Execute выполняется после вызова метода Resume (см. описание конструктора ниже).
Если поток рассчитан на однократное выполнение каких-либо действий, то никакого специального кода завершения внутри Execute писать не надо.
Если же в потоке будет выполняться какой-то цикл, и поток должен завершиться вместе с приложением, то условия окончания цикла должны быть примерно такими:
procedure TMyThread.Execute;
begin
repeat
DoSomething;
Until CancelCondition or Terminated;
end;
Здесь CancelCondition — ваше личное условие завершения потока (исчерпание данных, окончание вычислений, поступление на вход того или иного символа и т. п.), а свойство Terminated сообщает о завершении потока (это свойство может быть установлено как изнутри потока, так и извне; скорее всего, завершается породивший его процесс).
Конструктор объекта:
constructor Create(CreateSuspended: Boolean);
получает параметр CreateSuspended. Если его значение равно True, вновь созданный поток не начинает выполняться до тех пор, пока не будет сделан вызов метода Resume. В случае, если параметр CreateSuspended имеет значение False, конструктор завершается и только затем поток начинает исполнение.
destructor Destroy; override;
Деструктор Destroy вызывается, когда необходимость в созданном потоке отпадает. Деструктор завершает его и высвобождает все ресурсы, связанные с объектом TThread. function Terminate: Integer;
Для окончательного завершения потока (без последующего запуска) существует метод Terminate. Но если вы думаете, что этот метод делает какие-то принудительные действия по остановке потока, вы ошибаетесь. Все, что происходит, — это установка свойства
property Terminated: Boolean;
в значение True. Таким образом, Terminate — это указание потоку завершиться, выраженное "в мягкой форме", с возможностью корректно освободить ресурсы. Если вам нужно немедленно завершить поток, используйте функцию Windows API TerminateThread.
Примечание
Примечание
Метод Terminate автоматически вызывается и из деструктора объекта. Поток— объект VCL будет дожидаться, пока завершится поток— объект операционной системы. Таким образом, если поток не умеет завершаться корректно, вызов деструктора потенциально может привести к зависанию всей программы.
Еще одно полезное свойство:
property FreeOnTerminate: Boolean;
Если это свойство равно True, то деструктор потока будет вызван автоматически по его завершении. Это очень удобно для тех случаев, когда вы в своей программе не уверены точно, когда именно завершится поток, и хотите использовать его по принципу "выстрелил и забыл" (fire and forget).
function WaitFor: Integer;
Метод WaitFor предназначен для синхронизации и позволяет одному потоку дождаться момента, когда завершится другой поток. Если вы внутри потока FirstThread пишите код
Code := SecondThread.WaitFor;
то это означает, что поток FirstThread останавливается до момента завершения потока SecondThread. Метод WaitFor возвращает код завершения ожидаемого потока (см. свойство Returnvalue).
property Handle: THandle read FHandle;
property ThreadID: THandle read FThreadID;
Свойства Handle и ThreadID дают программисту непосредственный доступ к потоку средствами API Win32. Если разработчик хочет обратиться к потоку и управлять им, минуя возможности класса TThread, значения Handle и ThreadID могут быть использованы в качестве аргументов функций Win32 API. Например, если программист хочет перед продолжением выполнения приложения дождаться завершения сразу нескольких потоков, он должен вызвать функцию API waitForMuitipieObjects; для ее вызова необходим массив дескрипторов потоков.
property Priority: TThreadPriority;
Свойство Priority позволяет запросить и установить приоритет потоков. Приоритеты потоков в деталях описаны выше. Допустимыми значениями приоритета для объектов TThread являются tpidle, tpLowest, tpLower, tpNormai, tpHigher, tpHighest и tpTimeCritical.
procedure Synchronize(Method: TThreadMethod);
Этот метод относится к секции protected, т. е. может быть вызван только из потомков TThread. Delphi предоставляет программисту метод Synchronize для
безопасного вызова методов VCL внутри потоков. Во избежание конфликтных ситуаций, метод synchronize дает гарантию, что к каждому объекту VCL одновременно имеет доступ только один поток. Аргумент, передаваемый в метод Synchronize, — это имя метода, который производит обращение к VCL; вызов Synchronize с этим параметром — это то же, что и вызов самого метода. Такой метод (класса TThreadMethod) не должен иметь никаких параметров и не должен возвращать никаких значений. К примеру, в основной форме приложения нужно предусмотреть функцию
procedure TMainForm.SyncShowMessage; begin
ShowMessagedntToStr (ThreadListl. Count) ) ; // другие обращения к VCL
end;
а в потоке для показа сообщения писать не
ShowMessage(IntToStr(ThreadListl.Count));
и даже не
MainForm.SyncShowMessage;
а только так:
Synchronize(MainForm.SyncShowMessage);
Примечание
Примечание
Производя любое обращение к объекту VCL из потока, убедитесь, что при этом используется метод Synchronize; в противном случае результаты могут оказаться непредсказуемыми. Это верно даже в том случае, если вы используете средства синхронизации, описанные ниже.
procedure Resume;
Метод Resume класса TThread вызывается, когда поток возобновляет выполнение после остановки, или для явного запуска потока, созданного с параметром CreateSuspended, равным True.
procedure Suspend;
Вызов метода Suspend приостанавливает поток с возможностью повторного запуска впоследствии. Метод suspend приостанавливает поток вне зависимости от кода, исполняемого потоком в данный момент; выполнение продолжается с точки останова.
property Suspended: Boolean;
Свойство suspended позволяет программисту определить, не приостановлен ли поток. С помощью этого свойства можно также запускать и останавливать поток. Установив свойство suspended в значение True, вы получите тот же результат, что и при вызове метода Suspend — приостановку. Наоборот, установка свойства Suspended в значение False возобновляет выполнение потока, как и вызов метода Resume.
property ReturnValue: Integer;
Свойство ReturnValue позволяет узнать и установить значение, возвращаемое потоком по его завершении. Эта величина полностью определяется пользователем. По умолчанию поток возвращает ноль, но если программист захочет вернуть другую величину, то простая переустановка свойства ReturnValue внутри потока позволит получить эту информацию другим потокам. Это, к примеру, может пригодиться, если внутри потока возникли проблемы, или с помощью свойства ReturnValue нужно вернуть число не прошедших орфографическую проверку слов.
На этом завершим подробный обзор класса TThread. Для более близкого знакомства с потоками и классом Delphi TThread создадим многопоточное приложение. Для этого нужно написать всего несколько строк кода и несколько раз щелкнуть мышью.
Консольный ввод
Консольный ввод
Консольный ввод (console input) годится для потоков, которые должны ожидать отклика на нажатие пользователем клавиши на клавиатуре. Этот тип ожидания может быть использован в программе дуплексной связи (chat). Один поток при этом будет ожидать получения символов; второй — отслеживать ввод пользователя и затем отсылать набранный текст ожидающему приложению.
Критическая секция
Критическая секция
Работая в Delphi, программист может также использовать объект типа критическая секция (critical section). Критические секции подобны взаимным исключениям по сути, однако между ними существуют два главных отличия:
Возьмем класс TCriticalSection (модуль SYNCOBJS.PAS). Логика использования его проста — "держать и не пущать". В многопотоковом приложении создается и инициализируется общая для всех потоков критическая секция. Когда один из потоков достигает критически важного участка кода, он пытается захватить секцию вызовом метода Enter:
MySection.Enter; try DoSomethingCritical;
finally
MySection.Leave;
end;
Когда другие потоки доходят до оператора захвата секции Enter и обнаруживают, что она уже захвачена, они приостанавливаются вплоть до освобождения секции первым потоком путем вызова метода Leave. Обратите внимание, что вызов Leave помещен в конструкцию try. .finally — здесь требуется стопроцентная надежность. Критические секции являются системными объектами и подлежат обязательному освобождению — впрочем, как и остальные рассматриваемые здесь объекты.
Локальные данные потока
Локальные данные потока
Интересная проблема возникает, если в приложении будет несколько одинаковых потоков. Как избежать совместного использования одних и тех же переменных несколькими потоками? Первое, что приходит на ум, — добавить и использовать поля объекта — потомка TThread, которые можно добавить при его создании. Каждый поток соответствует отдельному экземпляру объекта, и их данные пересекаться не будут. (Кстати, это одно из больших удобств использования класса TThread.) Но есть функции API, которые знать не знают об объектах Delphi и их полях и свойствах. Для поддержки разделения данных между потоками на нижнем уровне в язык Object Pascal введена специальная директива — threadvar, которая отличается от директивы описания переменных var тем, что применяется только к локальным данным потока. Следующее описание:
Var
datal: Integer; threadvar
data2: Integer;
означает, что переменная datal будет использоваться всеми потоками данного приложения, а переменная data2 будет у каждого потока своя.
Обзор потоков
Обзор потоков
Определение потока довольно простое: потоки — это объекты, получающие время процессора. Время процессора выделяется квантами (quantum, time slice). Квант времени — это интервал, имеющийся в распоряжении потока до тех пор. пока время не будет передано в распоряжение другого потока.
Обратите внимание, что кванты выделяются не программам или процессам, а порожденным ими потокам. Как минимум, каждый процесс имеет хотя бы один (главный) поток, но современные операционные системы, начиная с Windows 95 (для приверженцев Borland Kylix и Linux также), позволяют запустить в рамках процесса несколько потоков.
Если вы новичок в использовании потоков, самый простой пример их использования — приложения из состава Microsoft Office. К примеру, пакеты Excel и Word задействуют по несколько потоков. Word может одновременно корректировать грамматику и печатать, при этом осуществляя ввод данных с клавиатуры и мыши; программа Excel способна выполнять фоновые вычисления и печатать.
Примечание
Примечание
Узнать число потоков, запущенных приложением, в Windows NT, 2000 и ХР можно при помощи утилиты Task Manager (Диспетчер задач). Для этого среди показателей, отображаемых в окне Processes, нужно выбрать опцию Thread Count. Так, в момент написания этих строк MS Word использовал 5 потоков, среда Delphi — 3.
Вполне возможно, что эту главу сейчас вы читаете из чистого любопытства. Но, более вероятно, вы пришли в поиске ответов на конкретные проблемы. Какого же рода проблемы могут быть решены с применением потоков?
Если задачи приложения можно разделить на различные подмножества: обработка событий, ввод/вывод, связь и др., то потоки могут быть органично встроены в программное решение. Если разработчик может разделить большую задачу на несколько мелких, это только повысит переносимость кода и возможности его многократного использования.
Сделав приложение многопоточным, программист получает дополнительные возможности управления им. Например, через управление приоритетами потоков. Если один из них "притормаживает" приложение, занимая слишком много процессорного времени, его приоритет может быть понижен.
Другое важное преимущество внедрения потоков — при возрастании "нагрузки" на приложение можно увеличить количество потоков и тем самым снять проблему.
Потоки упрощают жизнь тем программистам, которые разрабатывают приложения в архитектуре клиент/сервер. Когда требуется обслуживание нового клиента, сервер может запустить специально для этого отдельный поток. Такие потоки принято называть симметричными потоками (symmetric threads) — они имеют одинаковое предназначение, исполняют один и тот же код и могут разделять одни и те же ресурсы. Более того, приложения, рассчитанные на серьезную нагрузку, могут поддерживать пул (pool) однотипных потоков. Поскольку создание потока требует определенного времени, для ускорения работы желательно заранее иметь нужное число готовых потоков и активизировать их по мере подключения очередного клиента.
Примечание
Примечание
Такой подход особенно характерен для Web-сервера Microsoft Internet Information Services и приложений, обрабатывающих запросы в его среде. Если вы создаете приложения ISAPI на Delphi, то можете использовать пулинг потоков, подключив к проекту модуль ISAPIThreadPool.pas. Если вы хотите позаимствовать идеи для других целей, ознакомьтесь с содержимым этого модуля.
Асимметричные потоки (asymmetric threads) — это потоки, решающие различные задачи и, как правило, не разделяющие совместные ресурсы. Необходимость в асимметричных потоках возникает:
Оповещение об изменении в файловой системе
Оповещение об изменении в файловой системе
Этот вид объекта ожидания очень интересен и незаслуженно мало известен. Мы рассмотрели практически все варианты того, как один поток может подать сигнал другому. А как получить сигнал от операционной системы? Ну, например, о том, что в файловой системе произошли какие-то изменения? Такой вид оповещения позаимствован из ОС UNIX и доступен программистам, работающим с Win32. Для организации мониторинга файловой системы нужно использовать
Три функции — FindFirstChangeNotification, FindNextChangeNotification и FinddoseChangeNotification. Первая из них возвращает дескриптор объекта файлового оповещения, который можно передать в функцию ожидания. Объект активизируется тогда, когда в заданной папке произошли те или иные изменения (создание или уничтожение файла или папки, изменение прав доступа и т. д.). Вторая — готовит объект к реакции на следующее изменение. Наконец, с помощью третьей функции следует закрыть ставший ненужным объект.
Так может выглядеть код метода Execute потока, созданного для мониторинга файловой системы:
var DirName : string;
...
procedure TSimpleThread.Execute;
var r: Cardinal;
fn : THandle;
begin
fn := FindFirstChangeNotification(pChar(DirName), True,
FILEJTOTIFY_CHANGE_FILE_NAME);
repeat
r := WaitForSingleObject(fn,2000);
if r = WAIT_OBOECT_0 then
Synchronize(Forml.UpdateList);
if not FindNextChangeNotification(fn) then
break;
until Terminated;
FindCloseChangeNotification(fn);
end;
На главной форме должны находиться компоненты, нужные для выбора обследуемой папки, а также компонент TListBox, в который будут записываться имена файлов:
procedure TForml.ButtonlClick(Sender: TObject);
var dir : string; begin
if SelectDirectory(dir,[],0)
then begin
Editl.Text := dir; DirName := dir;
end;
end;
procedure TForml.UpdateList;
var SearchRec: TSearchRec;
begin
ListBoxl.Clear;
FindFirst(Editl.Text+'\*.*', faAnyFile, SearchRec); repeat ListBoxl.Items.Add(SearchRec.Name);
until FindNext(SearchRec) <> 0;
FindClose(SearchRec);
end;
Приложение готово. Чтобы оно стало полнофункциональным, предусмотрите в нем механизм перезапуска потока при изменении обследуемой папки.
Поток
Поток
Поток может ожидать другой поток точно так же, как и другой процесс. Ожидание можно организовать с помощью функций API (как в только что рассмотренном примере), но удобнее это сделать при помощи метода TThread.WaitFor.
Потоки и процессы
Потоки и процессы
Когда мы говорим "программа" (application), то обычно имеем в виду понятие, в терминологии операционной системы обозначаемое как "процесс". Процесс состоит из виртуальной памяти, исполняемого кода, потоков и данных. Процесс может содержать много потоков, но обязательно содержит, по крайней мере, один. Поток, как правило, имеет "в собственности" минимум ресурсов; он зависит от процесса, который и распоряжается виртуальной памятью, кодом, данными, файлами и другими ресурсами ОС.
Почему мы используем потоки вместо процессов, хотя, при необходимости, приложение может состоять и из нескольких процессов? Дело в том, что переключение между процессами — значительно более трудоемкая операция, чем переключение между потоками. Другой довод в пользу использования потоков — то, что они специально задуманы для разделения ресурсов; разделить ресурсы между процессами (имеющими раздельное адресное пространство) не так-то просто.
Пример создания многопоточного приложения в Delphi
Пример создания многопоточного приложения в Delphi
Этот раздел содержит описание шагов, необходимых для создания простого, но показательного примера многопоточного приложения. Мы будем пытаться вычислить число "пи" с максимальной точностью после запятой. Конечно, встроенная в Delphi константа Pi имеет достаточную точность, правильнее сказать — максимальную, допускаемую самым точным 10-байтным форматом для вещественных чисел Extended. Так что превзойти ее нам не удастся. Но этот пример использования потоков может послужить прологом для решения реальных задач.
Первый пример будет содержать два потока: главный (обрабатывающий ввод пользователя) и вычислительный; мы сможем изменять их свойства и наблюдать за реакцией. Итак, выполните следующую последовательность действий:
1. В среде Delphi откройте меню File и выберите пункт New Application.
2. Расположите на форме пять меток и один переключатель, как показано на Рисунок 29.2.
Переименуйте главную форму в fmMain.
3. Откройте меню File и выберите пункт Save Project As. Сохраните модуль как uMain, а проект — как Threads 1.
Приоритеты потоков
Приоритеты потоков
Интерфейс Win32 API позволяет программисту управлять распределением времени между потоками; это распространяется и на приложения, написанные на Delphi. Операционная система планирует время процессора в соответствии с приоритетами потоков.
Приоритет потока — величина, складывающаяся из двух составных частей: приоритета породившего поток процесса и собственно приоритета потока. Когда поток создается, ему назначается приоритет, соответствующий приоритету породившего его процесса.
В свою очередь, процессы могут иметь следующие классы приоритетов.
Примечание
Классы Above normal и Below normal появились впервые в Windows 2000.
Класс реального времени задает приоритет даже больший, чем у многих процессов операционной системы. Такой приоритет нужен для процессов, обрабатывающих высокоскоростные потоки данных. Если такой процесс не завершится за короткое время, пользователь почувствует, что система перестала откликаться, т. к. даже обработка событий мыши не получит времени процессора.
Использование класса High ограничено процессами, которые должны завершаться за короткое время, чтобы не вызвать сбойной ситуации. Пример — процесс, который посылает сигналы внешнему устройству; причем устройство отключается, если не получит своевременный сигнал. Если у вас возникли проблемы с производительностью вашего приложения, было бы неправильно решать их просто за счет повышения его приоритета до high — такой процесс также влияет на всю ОС. Возможно, в этом случае следует модернизировать компьютер.
Большинство процессов запускается в рамках класса с нормальным приоритетом. Нормальный приоритет означает, что процесс не требует какого-либо специального внимания со стороны операционной системы.
И наконец, процессы с фоновым приоритетом запускаются лишь в том случае, если в очереди Диспетчера задач нет других процессов. Обычные виды приложений, использующие такой приоритет, — это программы сохранения экрана и системные агенты (system agents). Программисты могут использовать фоновые процессы для организации завершающих операций и реорганизации данных. Примерами могут служить сохранение документа или резервное копирование базы данных.
Приоритеты имеют значения от 0 до 31. Процесс, породивший поток, может впоследствии изменить его приоритет; в этой ситуации программист имеет возможность управлять скоростью отклика каждого потока.
Базовый приоритет нити складывается из двух составляющих, однако это не означает, что он просто равен их сумме. Взгляните на соответствующие величины, которые показаны в табл. 29.1. Для потока, имеющего собственный приоритет THREAD_PRIORITY_IDLE, базовый приоритет будет равен 1, невзирая на приоритет породившего его процесса.
И еще для класса Normal приведены по два приоритета, снабженные буквами В (Background) и F (Foreground). Объяснение этому дается ниже.
Проблемы при синхронизации потоков
Проблемы при синхронизации потоков
К сожалению, простота создания потоков подчас "компенсируется" сложностью их применения. Две типичные проблемы, с которыми программист может столкнуться при работе с потоками, — это тупики (deadlocks) и гонки (race conditions).
Процесс. Порождение дочернего процесса
Процесс. Порождение дочернего процесса
Объект типа процесс (process) может быть использован для того, чтобы приостановить выполнение потока в том случае, если он для своего продолжения нуждается в завершении процесса. С практической точки зрения такая проблема встает, когда нужно в рамках вашего приложения исполнить приложение, созданное кем-то другим, или, к примеру, сеанс MS-DOS.
Рассмотрим, как, собственно, один процесс может породить другой. Вместо устаревшей и поддерживаемой только для совместимости функции winExec, перекочевавшей из прежних версий Windows, гораздо правильнее использовать более мощную:
function CreateProcess(IpApplicationName: PChar; IpCorranandLine: PChar;
IpProcessAttributes, IpThreadAttributes: PSecurityAttributes;
blnheritHandles: BOOL;
dwCreationFlags: DWORD; IpEnvironment: Pointer;
IpCurrentDirectory: PChar;
const IpStartupInfo: TStartupInfo;
var IpProcessInformation: TProcessInformation): BOOL;
Первые два параметра ясны — это имя запускаемого приложения и передаваемые ему в командной строке параметры. Параметр dwCreationFlags содержит флаги, определяющие способ создания нового процесса и его будущий приоритет. Использованные в приведенном ниже листинге флаги означают: CREATE_NEW_CONSOLE —будет запущено новое консольное приложение с отдельным окном; NORMAL_PRIORITY_CLASS — нормальный приоритет.
Структура TStartupInfo содержит сведения о размере, цвете, положении окна создаваемого приложения. В нижеследующем примере (листинг 29.1) используется поле wshowwindow: установлен флаг SW_SHOWNORMAL, означающий визуализацию окна с нормальным размером.
На выходе функции заполняется структура IpProcessInformation. В ней программисту возвращаются дескрипторы и идентификаторы созданного процесса и его первичного потока. Нам понадобится дескриптор процесса — в нашем примере создается консольное приложение, затем происходит ожидание его завершения. "Просигналит" нам об этом именно объект IpProcessInformation.hProcess.
Листинг 29.1. Порождение дочернего процесса
var
IpStartupInfo: TStartupInfo;
IpProcessInformation: TProcessInformation;
begin
FillChar(IpStartupInfo,Sizeof(IpStartupInfo),10);
IpStartupInfo.cb := Sizeof(IpStartupInfo};
IpStartupInfo.dwFlags := STARTFJJSESHOWWINDOW; IpStartupInfo.wShowWindow := SW_SHOWNORMAL;
if not CreateProcess(nil,
PChar('ping localhost'),
nil,
nil,
false,
CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS,
nil,
nil,
IpStartupInfo, IpProcessInformation) then
ShowMessage(SysErrorMessage(GetLastError; )
else
begin
WaitForSingleObject
(IpProcessInformation.hProcess, 10000); CloseHandle(IpProcessInformation.hProcess);
end;
end;
Резюме
Потоки, как и другие мощные инструменты, должны быть использованы с осторожностью и без злоупотреблений, поскольку могут возникнуть ошибки, которые очень трудно найти. Есть очень много доводов за использование потоков, но есть и доводы против этого. Работа с потоками будет проще, если учитывать нижеприведенные положения.
С помощью диалога Performance Options можно управлять алгоритмом назначения приоритетов
Рисунок 29.1. С помощью диалога Performance Options можно управлять алгоритмом назначения приоритетов

Теперь, разобравшись в приоритетах потоков, нужно обязательно сказать о том, как же их использует планировщик заданий для распределения процессорного времени.
Операционная система имеет различные очереди готовых к выполнению потоков — для каждого уровня приоритета свой. В момент распределения нового кванта времени она просматривает очереди — от высшего приоритета к низшему. Готовый к выполнению поток, стоящий первым в очереди, получает этот квант и перемещается в хвост очереди. Поток будет исполняться всю продолжительность кванта, если не произойдет одно из двух событий:
Так что нормальная практика для асимметричных потоков — это назначение потоку, обрабатывающему ввод, более высокого приоритета, а всем остальным — более низкого или даже приоритета idle, если этот поток должен выполняться только во время простоя системы.
Семафор
Семафор
Семафор (semaphore) подобен взаимному исключению. Разница между ними в том, что семафор может управлять количеством потоков, которые имеют к нему доступ. Семафор устанавливается на предельное число потоков, которым доступ разрешен. Когда это число достигнуто, последующие потоки будут приостановлены, пока один или более потоков не отсоединятся от семафора и не освободят доступ.
В качестве примера использования семафора рассмотрим случай, когда каждый из группы потоков работает с фрагментом совместно используемого пула памяти. Так как совместно используемая память допускает обращение к ней только определенного числа потоков, все прочие должны быть блокированы вплоть до момента, когда один или несколько пользователей пула откажутся от его совместного использования.
простейший выбор для задач синхронизации.
Событие
Объект типа событие (event) — простейший выбор для задач синхронизации. Он подобен дверному звонку — звенит до тех пор, пока его кнопка находится в нажатом состоянии, извещая об этом факте окружающих. Аналогично, и объект может быть в двух состояниях, а "слышать" его могут многие потоки сразу.
Класс TEvent (модуль SYNCOBJS.PAS) имеет два метода: setEvent и ResetEvent, которые переводят объект в активное и пассивное состояние соответственно. Конструктор имеет следующий вид:
constructor Create(EventAttributes: PSecurityAttributes;
ManualReset, InitialState: Boolean; const Name: string);
Здесь параметр initialstate — начальное состояние объекта, ManualReset — способ его сброса (перевода в пассивное состояние). Если этот параметр равен True, событие должно быть сброшено вручную. В противном случае событие сбрасывается по мере того, как стартует хоть один поток, ждавший данный объект.
На третьем методе:
TWaitResult = (wrSignaled, wrTimeout, wrAbandoned, wrError);
function WaitFor(Timeout: DWORD): TWaitResult;
остановимся подробнее. Он дает возможность ожидать активизации события в течение Timeout миллисекунд. Как вы могли догадаться, внутри этого метода происходит вызов функции waitFotsingieObject. Типичных результатов на выходе waitFor два — wrsignaied, если произошла активизация события, и wrTimeout, если за время тайм-аута ничего не произошло.
Примечание
Примечание
Если нужно (и допустимо!) ждать бесконечно долго, следует установить параметр Timeout в значение INFINITE.
Рассмотрим маленький пример. Включим в состав нового проекта объект типа TThread, наполнив его метод Execute следующим содержимым:
Var res: TWaitResult;
procedure TSimpleThread.Execute;
begin
e := TEvent.Create(nil,True,false, 'test');
repeat
e.ReSetEvent;
res := e.WaitFor(10000);
Synchronize(Showlnfo);
until Terminated; e.Free;
end;
procedure TSimpleThread.Showlnfo;
begin
ShowMessage(IntToStr(Integer (res)));
end;
На главной форме разместим две кнопки — нажатие одной из них запускает поток, нажатие второй активизирует событие:
procedure TForml.ButtonlClick(Sender: TObject);
begin
TSimpleThread.Create(False);
end;
procedure TForml.Button2Click(Sender: TObject);
begin
e.SetEvent;
end;
Нажмем первую кнопку. Тогда появившийся на экране результат (метод Showlnfo) будет зависеть от того, была ли нажата вторая кнопка или истекли отведенные 10 секунд.
События используются не только для работы с потоками — некоторые процедуры операционной системы автоматически переключают их. К числу
таких процедур относятся отложенный (overlapped) ввод/вывод и события, связанные с коммуникационными портами.
Средства синхронизации потоков
Средства синхронизации потоков
Проще всего говорить о синхронизации, если создаваемый поток не взаимодействует с ресурсами других потоков и не обращается к VCL. Допустим, у вас на компьютере несколько процессоров, и вы хотите "распараллелить" вычисления. Тогда вполне уместен следующий код:
MyCompThread := TComputationThread.Create(False);
// Здесь можно что-нибудь делать, пока второй поток производит вычисления
DoSomeWork;
// Теперь ожидаем его завершения
MyCompThread.WaitFor;
Приведенная схема совершенно недопустима, если во время своей работы поток MyCompThread обращается к VCL посредством метода synchronize. В этом случае поток ждет главный поток для обращения к VCL, а тот, в свою очередь, его — классический тупик.
За "спасением" следует обратиться к программному интерфейсу Win32. Он предоставляет богатый набор инструментов, которые могут понадобиться для организации совместной работы потоков.
Главные понятия для понимания механизмов синхронизации — функции ожидания и объекты синхронизации. В Windows API предусмотрен ряд функций, позволяющих приостановить выполнение вызвавшего эту функцию потока вплоть до того момента, как будет изменено состояние какого-то объекта, называемого объектом синхронизации (под этим термином здесь понимается не объект Delphi, а объект операционной системы). Простейшая из этих функций — waitForSingieCbject — предназначена для ожидания одного объекта.
К возможным вариантам относятся четыре объекта, которые разработаны специально для синхронизации: событие (event), взаимное исключение (mutex), семафор (semaphore) и таймер (timer).
Но кроме специальных объектов можно организовать ожидание и других объектов, дескриптор которых используется в основном для иных целей, но может применяться и для ожидания. К ним относятся: процесс (process), поток (thread), оповещение об изменении в файловой системе (change notification) и консольный ввод (console input).
Косвенно к этой группе может быть добавлена критическая секция (critical section).
Примечание
Примечание
Перечисленные выше средства синхронизации в основном инкапсулированы в состав классов Delphi. У программиста есть две альтернативы. С одной стороны, в состав библиотеки VCL включен модуль SYNCOBJS.PAS, содержащий классы для события (TEvent) и критической секции (TCriticalSection). С другой, с Delphi поставляется отличный пример IPCDEMOS, который иллюстрирует проблемы взаимодействия процессов и содержит модуль IPCTHRD.PAS с аналогичными классами — для того же события, взаимного исключения (TMutex), а также совместно используемой памяти (TSharedMem).
Перейдем к подробному описанию объектов, используемых для синхронизации.
Классы процессов и приоритеты их потоков (для Windows 2000 и ХР)
Таблица 29.1. Классы процессов и приоритеты их потоков (для Windows 2000 и ХР)
|
IDLE_ PRIORITY CLASS
|
BELOW_ NORMAL PRIORITY CLASS
|
NORMAL_ PRIORITY_ CLASS
|
ABOVE_ NORMAL_ PRIORITY_ CLASS
|
HIGH PRIORITY CLASS
|
REALTIME PRIORITY CLASS
|
|
|
THREAD_ PRIORITY_
IDLE |
1 |
1 |
1 |
1 |
1 |
16 |
|
THREAD_ PRIORITY LOWEST
|
2 |
4 |
5(B) 7(F) |
8 |
11 |
22 |
|
THREAD_ PRIORITY_ BELOW NORMAL
|
3 |
5 |
6(B) 8(F) |
9 |
12 |
23 |
|
THREAD_ PRIORITY_ NORMAL
|
4 |
6 |
7(B) 9(F) |
10 |
13 |
24 |
|
THREAD PRIORITY_ ABOVE_ NORMAL
|
5 |
7 |
8(В) 10(F) |
11 |
14 |
25 |
|
THREAD_ PRIORITY_ HIGHEST
|
6 |
8 |
9(B) 11(F) |
12 |
15 |
26 |
|
THREAD_ PRIORITY TIME CRITICAL
|
15 |
15 |
15 |
15 |
15 |
31 |
К тому же Windows 2000 Professional и Windows 2000 Server имеют разные алгоритмы выделения квантов времени. Первая — клиентская — операционная система выделяет время короткими квантами переменной длины для ускорения реакции на приложения переднего плана (foreground). Для сервера же более важна стабильная работа системных служб, поэтому во второй ОС система распределяет длинные кванты постоянной длины.
Тупики
Тупики
Вероятно, вы не раз наблюдали на трамвайной остановке следующую забавную картину (Рисунок 29.6).

Рис 29.6. Ситуации тупиков возникают не только в программировании
Рисунок дает исчерпывающее пояснение ситуации тупиков. Тупики имеют место, когда поток ожидает ресурс, который в данный момент принадлежит другому потоку. Рассмотрим пример. Поток 1 захватывает ресурс А, и для того чтобы продолжать работу, ждет возможности захватить ресурс Б. В то же время Поток 2 захватывает ресурс Б и ждет возможности захватить ресурс А. Развитие этого сценария заблокирует оба потока; ни один из них не будет исполняться. Ресурсами могут выступать любые совместно используемые объекты системы — файлы, массивы в памяти, устройства ввода/вывода и т. п.
В ситуации на картинке три трамвая захватили по одному ресурсу (перекрестку) и пытаются захватить еще один, что, очевидно, невозможно без освобождения уже захваченных. В жизни ситуация разрешилась просто — самый молодой из водителей был вынужден отъехать. В информационных технологиях все бывает сложнее. Откройте любой документ, сопровождающий очередной пакет обновления к любой версии Windows. Очень часто там можно найти информацию об одной-двух исправленных ситуациях тупиков.
Внешний вид формы для приложения
Рисунок 29.2. Внешний вид формы для приложения Threads'1

4. Откройте меню File и выберите пункт New. Затем дважды щелкните на объекте типа поток (значок Thread Object). Откроется диалоговое окно New Items, показанное на Рисунок 29.3.
Пока один из авторов писал
Рисунок 29.5. Выполняющееся приложение Threads1

Пока один из авторов писал текст этого раздела, запущенное одновременно приложение Threadsl выполнило пять миллиардов итераций и приблизилось к встроенному значению Pi в десятом разряде. Интересно, насколько хватит терпения у вас?
Этот простой пример — первый шаг в усвоении того, как от базового класса rrhread можно порождать собственные классы. Из-за своей простоты он не лишен недостатков; более того — если бы вычислительных нитей было не одна, а более, кое-какие приемы были бы даже ошибочными. Но — об этом ниже.
Взаимные исключения
Взаимные исключения
Объект типа взаимное исключение (mutex) позволяет только одному потоку в данное время владеть им. Если продолжать аналогии, то этот объект можно сравнить с эстафетной палочкой.
Класс, инкапсулирующий взаимное исключение, — TMutex — находится в модуле IPCTHRD.PAS (пример IPCDEMOS). Конструктор:
constructor Create (const Name: string);
задает имя создаваемого объекта. Первоначально он не принадлежит никому. (Но функция API createMutex, вызываемая в нем, позволяет передать созданный объект тому потоку, в котором это произошло.) Далее метод
function Get(TimeOut: Integer): Boolean;
производит попытку в течение Timeout миллисекунд завладеть объектом (в этом случае результат равен True). Если объект более не нужен, следует вызвать метод
function Release: Boolean;
Программист может использовать взаимное исключение, чтобы избежать считывания и записи общей памяти несколькими потоками одновременно.
Компоненты Rave Reports и отчеты в приложении Delphi
Главная форма проекта DemoMDCube
Рисунок 30.5. Главная форма проекта DemoMDCube

Компонент Decisioncubel выполняет всю работу по созданию многомерного представления набора данных компонента DecisionQueryl. Причем, практически все делается без вмешательства пользователя.
При настройке компонента был использован специализированный редактор свойства DecisionMap. В нем были заданы названия для размерностей и значения для расчета максимального размера используемой памяти. Все заданные значения соответствуют рекомендованным.
Для связывания набора данных с визуальными компонентами использован компонент DecisionSource1. На него замыкаются все три визуальных компонента многомерного представления, использованные в проекте.
Управление осуществляется компонентом DecisionPivotl. Основной визуальный компонент DecisionGrid1 представляет многомерный набор данных в табличном виде. В исходном состоянии по горизонтали расположены размерности номеров накладных и наименований, по вертикали размешаются размерности дат заказов и покупателей.
Дополнительного программного кода проект не имеет.
Компонент TDecisionCube
Компонент TDecisionCube
Компонент TDecisionCube осуществляет преобразование набора данных, который содержится в компоненте TDecisionQuery, к виду, доступному для отображения визуальными компонентами многомерного представления данных (табл. 30.2). Обычную таблицу набора данных компонент преобразует в многомерный кросстаб. Число размерностей создаваемого кросстаба зависит от числа полей данных набора данных. Значения в ячейках кросстаба зависят от типа агрегатной функции в запросе SQL.
Компонент TDecisionGraph
Компонент TDecisionGraph
Компонент TDecisionGraph создает график на основе многомерного представления набора данных. Конкретный вид графика (назначение горизонтальной и вертикальной осей) зависит от настроек компонентов TDecisionCube и TDecisionPivot. По умолчанию к оси абсцисс привязывается первая вертикальная размерность, к оси ординат — первая сумма. Первая горизонтальная размерность отображается в легенде графика.
Одним из предков компонента TDecisionGraph является класс TChart, от которого унаследованы все многочисленные свойства и методы для настройки графика.
Для подключения к графику набора данных используется свойство
property DecisionSource: TDecisionSource;
которое ссылается на экземпляр компонента источника данных.
Сразу после подключения автоматически строится график с осями, заданными по умолчанию.
Для управления графиком во время выполнения можно использовать компонент TDecisionPivot.
Компонент TDecisionGrid
Компонент TDecisionGrid
Компонент TDecisionGrid предназначен для многомерного представления данных в табличном виде. Параметры отображаемого набора данных (какие размерности будут видны при открытии, как группировать данные, как управлять размерностями) настраиваются при помощи других компонентов многомерного представления. В компоненте TDecisionGrid можно настроить только свойства самой сетки.
Для управления графиком во время выполнения можно использовать компонент TDecisionPivot.
В табл. 30.5 приведены основные свойства и методы компонента.
Компонент TDecisionPivot
Компонент TDecisionPivot
Компонент TDecisionPivot предоставляет пользователю средства управления размерностями многомерного представления данных. В стандартном состоянии компонент представляет собой панель, разделенную на три части (табл. 30.6). Каждая часть имеет собственный набор кнопок.
Левая часть имеет единственную кнопку, щелчок на которой позволяет сделать выбор суммирующего поля из развернувшегося списка. Элементы списка соответствуют вычисляемым полям с использованием агрегатных функций из запроса соответствующего компонента TDecisionQuery.
Выбор поля приводит к изменению значений в ячейках кросстаба. Например, выбор поля с функцией зим изменит значения в ячейках на суммы полей, поле с функцией COUNT произведет подсчет количества элементов в полях.
Средняя часть панели предназначена для размещения кнопок, соответствующих горизонтальным размерностям.
Правая часть панели используется для кнопок, соответствующих вертикальным размерностям.
Кнопка в нажатом состоянии показывает размерность. Одновременно отображается и общая сумма по размерности.
Размерности можно менять местами и перемещать с вертикали на горизонталь и обратно. Для этого можно выбрать команду Moved to Column Area из всплывающего меню кнопки. Во время выполнения можно использовать обычное перетаскивание кнопок при помощи мыши.
Всплывающее меню кнопки размерности имеет еще одну команду — Drilled In. По этой команде размерность переходит в режим детального просмотра по каждому значению поля. Конкретное значение можно выбрать из списка, который открывается при щелчке на кнопке. В этом случае суммы в ячейках кросстаба рассчитываются не по всей размерности, а только по выбранному значению.
Компонент TDecisionQuery
Компонент TDecisionQuery
Компонент доступа к данным TDecisionQuery предназначен для создания набора данных, который был бы пригоден для многомерного представления. Для создания набора данных используется запрос SQL. Требования к запросу приведены выше.
Этот компонент является прямым наследником компонента TQuery и не имеет собственных свойств и методов. Для создания запросов можно воспользоваться специализированным редактором, который автоматизирует этот процесс.
Компонент TDecisionSource
Компонент TDecisionSource
Компонент TDecisionSource предназначен для связывания визуальных компонентов многомерного представления с компонентом TDecisionCube (табл. 30.4). Кроме того, за счет возможности подключения к этому компоненту нескольких визуальных компонентов одновременно, при изменении состояния одного визуального компонента осуществляется синхронизация многомерного представления во всех остальных компонентах.
От набора данных в визуальные компоненты передаются данные, обратно транслируются команды пользователя по управлению многомерным представлением данных.
Отображение данных
Отображение данных
При работе с кросстабом пользователь имеет дело с двумя визуальными компонентами многомерного представления данных. Это компонент TDecisionGrid, который представляет данные в табличном виде, и компонент TDecisionGraph, который представляет данные в виде графика.
С компонентом TDecisionCube они связаны при помощи компонента TDecisionSource (см. выше).
Данные в визуальных компонентах появляются после открытия набора данных в соответствующем компоненте TDecisionQuery. Причем эти компоненты полностью работоспособны уже во время разработки приложения.
Подготовка набора данных
Подготовка набора данных
Компоненты многомерного представления данных работают со специально созданным и подготовленным набором данных. Эта работа выполняется специальным компонентом доступа к данным — TDecisionQuery. Его непосредственным предком является компонент TQuery.
Набор данных формируется при помощи запроса, который основан на стандартном синтаксисе SQL 92. Для обеспечения работы многомерного представления данных запрос должен удовлетворять ряду требований.
1. В тексте запроса должны присутствовать только те поля, которые разработчик хочет показать в компонентах многомерного представления данных.
2. Поля запроса должны быть сгруппированы при помощи оператора GROUP BY.
3. Запрос должен содержать агрегатные функции, которые определяют вид
информации, отображаемой в ячейках кросстаба.
Компонент TDecisionQuery должен только обеспечить выполнение запроса и создание набора данных, он не имеет никаких дополнительных свойств или методов. Поэтому для создания набора данных можно использовать и обычный компонент TQuery. Преимущество компонента TDecisionQuery состоит в том, что он имеет специализированный редактор для создания текста запроса (Рисунок 30.2). Он вызывается командой Decision Query Editor из всплывающего меню компонента или двойным щелчком на компоненте. Элементы управления страницы Dimensions/Summaries позволяют создавать текст запроса, манипулируя именами полей таблиц. Псевдоним базы данных выбирается в комбинированном списке Database. После этого в списке Table задается нужная таблица. Если в запросе требуется использовать несколько таблиц, то для их выбора можно воспользоваться утилитой SQL Builder, которая вызывается щелчком на одноименной кнопке.
Понятие кросстаба
Понятие кросстаба
Обычная таблица данных имеет строго заданное число столбцов, причем каждый столбец всегда предназначен для представления данных из одного поля. Для кросстаба число и назначение столбцов зависит от значений какого-либо поля. Число строк в кросстабе не равно числу строк в таблице БД, а также зависит от значений какого-либо поля. В ячейках кросстаба всегда располагается суммирующая информация по значениям полей горизонтали и вертикали (Рисунок 30.1).
Создать подобную двумерную структуру отображения данных при помощи обычных компонентов со страницы Data Controls Палитры компонентов очень непросто и хлопотно.
Пример кросстаба
Рисунок 30.1. Пример кросстаба

В общем случае горизонтальную и вертикальную структуры кросстаба могут составлять несколько полей одновременно, которые сгруппированы относительно более общих полей.
Для создания наборов данных, которые можно представить в виде кросстаба, используются запросы SQL с применением группирующего оператора GROUP BY и агрегатных функций. Если обратиться к топологическим аналогиям, то набор данных такого запроса представляет собой многомерный гиперкуб, каждая сторона которого соответствует одному полю набора данных.
Для дальнейшего изложения необходимо ввести еще одно понятие. Совокупность строк или колонок, имеющих отношение к одному полю набора данных, будем называть размерностью. Размерность представляет собой виртуальную плоскость, которая рассекает многомерный куб данных параллельно какой-либо стороне этого куба. Компоненты многомерного представления данных как раз предназначены для того, чтобы визуализировать это n-мерное сечение.
Переходя от пространственных моделей к наборам данных, можно сказать, что размерность представляет собой совокупность значений какого-либо поля в кросстабе относительно других полей.
Пример многомерного представления данных
Пример многомерного представления данных
В качестве примера использования многомерного представления данных рассмотрим демонстрационное приложение DemoMDCube (Рисунок 30.5).
В качестве исходного набора данных используется запрос к таблицам SALES и CUSTOMER общедоступной базы данных EMPLOYEE.GDB в составе поставки InterBase следующего вида:
SELECT С.CUSTOMER, S.ORDERJDATE, SUM(S.DISCOUNT) FROM SALES S
INNER JOIN CUSTOMER С
ON (C.CUST_NO = S.CUST_NO) GROUP BY C.CUSTOMER, S.ORDER_DATE
Запрос удовлетворяет всем требованиям для обеспечения многомерного представления данных. Для выполнения запроса и создания набора данных в проекте существует компонент DecisionQueryl.
Набор данных компонента открыт во время разработки, поэтому все компоненты многомерного представления в проекте ведут себя так же, как и во время выполнения программы.
С ним связан компонент Decisioncubel, для которого свойство имеет следующее значение:
DecisionCubel.DataSet := DecisionQueryl;
Многомерное представление данных позволяет проводить
Резюме
Многомерное представление данных позволяет проводить сложный анализ информации, содержащейся в базах данных. Основой многомерного представления является группирующий запрос (с оператором GROUP BY). С точки зрения пользователя анализ с помощью многомерного представления данных очень прост в использовании.
Специализированный редактор компонента TDecisionQuery
Рисунок 30.2. Специализированный редактор компонента TDecisionQuery

Из списка доступных полей при помощи кнопок Add требуемые поля можно перенести в список полей — размерностей Dimensions и список суммирующих полей Summaries. Поля из этих списков используются при создании запроса.
Запрос формируется автоматически при работе с описанными элементами управления. Текст запроса доступен для просмотра и редактирования на странице SQL Query.
Подготовка набора данных к многомерному представлению осуществляется компонентом TDecisionCube. Его основная задача — создание размерностей для многомерной структуры данных на основе каждого поля набора данных. Для каждой размерности в компоненте можно задать ряд параметров, которые определяют ее поведение и внешний вид.
Компонент TDecisionCube формирует размерности при открытии набора данных, причем созданное многомерное представление данных полностью работоспособно уже во время разработки. Для этого достаточно присвоить свойству Active Компонентов TDecisionQuery или TQuery значение True. После этого любой визуальный компонент многомерного представления начинает работать так же, как и во время выполнения.
Компонент TDecisionCube также позволяет управлять использованием памяти многомерного представления данных. Дело в том, что при добавлении к многомерному представлению новой размерности объем занимаемой памяти возрастает в арифметической прогрессии. Поэтому возможность ограничения размеров используемой памяти особенно актуальна для больших наборов данных.
Все основные настройки компонента выполняются при помощи специализированного редактора свойства DecisionMap (Рисунок 30.3).
Специализированный редактор свойства DecisionMap компонента TDecisionCube (страница Memory Control)
Рисунок 30.4. Специализированный редактор свойства DecisionMap компонента TDecisionCube (страница Memory Control)
Аналогичные значения в ряду Current показывают текущее число этих структур.
Аналогичные значения в ряду Active+Needed показывают общее возможное число размерностей, сумм и ячеек.
Значения в ряду Active показывают число видимых размерностей сумм и ячеек.
Кнопка Get Cell Counts выполняет запрос, который возвращает число ячеек в кросстабе.
Группа радиокнопок Designer Data Options задает режим показа данных во время разработки:
Кроме этого, компонент TDecisionSource позволяет разработчику установить общие для всех связанных с ним визуальных компонентов многомерного представления данных настройки отображения данных.
Специализированный редактор свойства
Рисунок 30.3. Специализированный редактор свойства DecisionMap компонента TDecisionCube (страница Dimension Settings)

Для настроек размерностей используется страница Dimension Settings этого редактора. В расположенном слева списке Available Fields содержатся все поля набора данных. В элементах управления справа приведены параметры размерности для выбранного поля.
В однострочном редакторе Display Name задается название поля, которое будет присутствовать в визуальных компонентах многомерного просмотра.
Неактивный список выбора Туре показывает, является ли поле основой для размерности или суммы.
Список выбора Active Type определяет, когда данные поля появляются в визуальных компонентах. Его элементы обозначают следующее:
Комбинированный список Grouping необходим для того, чтобы определить, какие значения будут показаны. Варианты Year, Quarter, Month возможны только для полей с календарным типом данных.
Однострочный редактор Initial Value задает начальное значение для поля.
Страница Memory Control используется для управления расходом памяти для нужд компонента (Рисунок 30.4). Однострочные редакторы Dimensions, Summaries и Cells в ряду Maximum позволяют задать максимальное число размерностей, сумм и ячеек, соответственно.
Как связать компоненты многомерного представления данных
Таблица 30.1. Как связать компоненты многомерного представления данных
|
Свойство
|
Значение
|
Описание
|
|
TBecisionCube
|
||
|
DataSet
|
DecisionQuery1
|
Определяет компонент доступа к данным, который создает набор данных |
|
TDecisionSource
|
||
|
DecisionCube
|
DecisionCubel
|
Указывает на компонент формирования многомерного набора данных |
|
TDecisionGrid
|
||
|
DecisionSource
|
DecisionSourcel
|
Ссылается на компонент TDecisionSource
|
TDecisionPivot
|
|
DecisionSource
|
DecisionSourcel
|
Ссылается на компонент TDecisionSource
|
Теперь, когда мы узнали, как объединить компоненты многомерного представления данных в единую систему, настало время более подробно изучить возможности каждого компонента.
Свойства и методы компонента TDecisionCube
Таблица 30.2. Свойства и методы компонента TDecisionCube
|
Объявление
|
Тип
|
Описание
|
|
Свойства
|
||
|
property Active: Boolean;
|
Pu
|
Разрешает или запрещает преобразование набора данных в кросстаб |
property BinData: Boolean;
|
Ro
|
Значение True означает, что хотя бы одна размерность находится в свернутом состоянии (данные не отображаются) |
|
property Capacity: Integer;
|
Pu
|
Определяет число байтов, используемых для хранения многомерного массива |
|
property CurrentSuramary: Integer;
|
Pu
|
Содержит индекс текущей суммы кросстаба |
|
property DataSet: TDataSet;
|
Pb
|
Ссылка на экземпляр набора данных, который отображается в кросстабе |
|
type TCubeDesignState = (dsNoData, dsMetaData, dsDimensionData, dsAHData);
property DesignState: TCubeDesignState; |
Pu
|
Задает режим отображения данных в кросстабе: |
|
property DimensionCount: Integer;
|
Ro
|
Возвращает число размерностей |
|
property DimensionMap: TCubeDims;
|
Pb
|
Индексированный список ссылок на объекты параметров размерностей |
|
property DimensionMapCount: Integer;
|
Pb
|
Общее число полей набора данных, включая поля размерностей и сумм |
|
property MaxCells: Integer;
|
Pb
|
Задает максимальное число ячеек кросстаба |
|
property MaxDimensions: Integer;
|
Pb
|
Задает максимальное число размерностей |
|
property MaxSummaries: Integer;
|
Pb
|
Задает максимальное число сумм |
|
property ShowProgressDialog: Boolean;
|
Pb
|
При значении True при подготовке кросстаба отображается индикатор |
|
property SummaryCount: Integer;
|
Ro
|
Возвращает число активных сумм кросстаба |
|
Методы
|
||
|
function GetDetailSQL (ValueArray : TSmalllntArray; SelectList: string; bActive: Boolean) : string;
|
Pu
|
Возвращает текст запроса SQL, который может быть использован для создания набора данных, включающего данные из кросстаба без сумм |
function GetSQL(ValueArray: TSrralllntArray; bActive: Boolean): string;
|
Pu
|
Возвращает текст запроса SQL, который может быть использован для создания набора данных, включающего данные из кросстаба без сумм |
|
procedure ShowCubeDialog;
|
Pu
|
Вызывает специализированный редактор компонента |
|
procedure Refresh (DimensionMap : TCubeDims; bForce: Boolean) ;
|
Обновляет список объектов параметров размерностей |
|
|
Методы-обработчики событий
|
||
|
type TCubeRefreshEvent = procedure (DataCube: TCustomDataStore; DimMap: TCubeDims) of object;
property OnRefresh: TCubeRefreshEvent; property AfterClose: TCubeNotifyEvent; |
Pb
|
Вызывается сразу после закрытия компонента (Active := False) |
|
property AfterOpen: TCubeNot if yEvent;
|
Pb
|
Вызывается сразу после открытия компонента (Active := False)
|
|
property BeforeClose: TCubeNotif yEvent ;
|
Pb
|
Вызывается перед закрытием компонента (Active := False)
|
|
property BeforeOpen: TCubeNotifyEvent;
|
Pb
|
Вызывается перед открытием компонента (Active := False)
|
|
TErrorAction = (eaFail, eaContinue) ;
TCapacityErrorEvent = procedure (var EAction: TErrorAction) of object; property OnLowCapacity: TCapacityErrorEvent ; |
Pb
|
Вызывается после того, как занимаемый кросстабом объем памяти превысит заданный предел |
Ключевым свойством компонента является свойство DecisionMap, которое позволяет установить параметры размерностей и максимальный размер используемой памяти. Для этих целей применяется специализированный редактор (см. Рисунок 30.4).
Это свойство представляет собой экземпляр класса TCubeDims, который инкапсулирует индексированный список экземпляров объектов TCubeDim, каждый из которых содержит информацию о параметрах одной размерности. Основные свойства этого класса представлены в табл. 30.3.
Основные свойства класса TCubeDim
Таблица 30.3. Основные свойства класса TCubeDim
|
Объявление
|
Тип
|
Описание
|
|
type TActiveFlags = (diActive, diAsNeeded, dilnactive);
property ActiveFlag: TActiveFlags; |
Pb
|
Определяет режим отображения данных размерности |
|
property BaseName: string;
|
Pb
|
Содержит имя поля размерности в таблице базы данных |
|
property BinFormat: string;
|
Pu
|
Определяет способ форматирования диапазона значений размерности |
|
type TBinType = (binNone, binYear, binQuarter, binMonth, binSet, binCustom) ;
property BinType: TBinType; |
Pb
|
Определяет способ группирования данных в размерности |
|
type TDimFlags = (dimDimension, dimSum, dimCount, dimAverage, dimMin, dimMax, dimGenericAgg, dimUnknown) ;
property DimensionType: TDimFlags ; |
Pb
|
Определяет тип размерности |
|
property FieldName: String;
|
Pb
|
Содержит имя поля в наборе данных |
|
property FieldType: TFieldType;
|
Pu
|
Определяет тип поля |
|
property Format: String;
|
Pu
|
Задает форматирование данных размерности |
|
property Loaded: Boolean;
|
Ro
|
Значение True говорит о том, что данный элемент загружен в многомерный набор данных |
property StartDate: TDate;
|
Pu
|
Определяет начальный элемент для группировки по дате |
|
property StartValue: String;
|
Pu
|
Определяет начальный элемент : для группировки по значению |
|
property ValueCount: Integer;
|
Pb
|
Возвращает число уникальных элементов в размерности |
Основные свойства компонента TDecisionSource
Таблица 30.4. Основные свойства компонента TDecisionSource
|
Объявление
|
Описание
|
|
type TDecisionControlType= (xtCheck, xtRadio, xtRadioEx) ;
property ControlT ype: TDecisionControlType; |
Определяет способ управления отдельной размерностью в компоненте TDecisionGrid |
|
property CurrentSum: Integer;
|
Содержит индекс текущей суммы в компоненте TDecisionGrid
|
|
property DecisionCube: TDecisionCube;
|
Связывает данный компонент с компонентом TDecisionCube. Содержит ссылку на экземпляр компонента TDecisionCube
|
|
property Ready: Boolean;
|
Значение True означает, что данный компонент связан с активным компонентом TDecisionCube
|
|
property SparseCols: Boolean;
|
При значении True из компонента TDecisionGrid удаляются пустые колонки |
property SparseRows: Boolean; |
При значении True из компонента TDecisionGrid удаляются пустые строки |
Свойства и методы компонента TDecisionGrid
Таблица 30.5. Свойства и методы компонента TDecisionGrid
|
Объявление
|
Тип
|
Описание
|
| Свойства | ||
|
property Cells [ACol, ARow: Integer] : string;
|
Ro
|
Индексированный массив значений всех ячеек компонента в строковом формате |
|
property ColCount : Integer;
|
RO
|
Возвращает общее число колонок в сетке |
|
property DecisionSource: TDecisionSource;
|
Рb
|
Указывает на компонент TDecisionSource, через который осуществляется связь с набором данных |
|
property Dimensions: TDisplayDims;
|
Pb
|
Объект TDisplayDims представляет индексированный список объектов визуальных свойств размерностей |
|
property FixedCols: Integer;
|
Ro
|
Возвращает число фиксированных колонок, которые используются для отображения информации о размерностях (названия, значения, обозначения) |
|
property FixedRows: Integer;
|
Ro
|
Возвращает число фиксированных строк, которые используются для отображения информации о размерностях (названия, значения, обозначения) |
|
type
TDecisionGridOption = (cgGridLines, cgOutliner, cgPivo table) ; TDecisionGridOptions = set of TDecisionGridOption; property Options: TDecisionGridOptions; |
Pb
|
Определяет общие настройки компонента: |
|
property RcwCount: Integer;
|
Ro
|
Возвращает общее число строк в сетке |
property ShowCubeEditor: Boolean;
|
Pb
|
Разрешает или запрещает использование специализированного редактора компонента TDecisionCube |
|
property Totals: Boolean;
|
Pu
|
При значении True сетка имеет промежуточные суммы по каждой колонке и строке |
|
Методы
|
||
|
type
TDecisionDrawStates = i (dsGroupStart, dsRowCaption, dsColCaption, dsSum, dsRowValue, dsColValue, dsData, dsOpenAfter, dsCloseAfter, dsCloseBefore, dsOpenBefore, dsRowIndicator, dsColIndicator, dsRowPlus, dsColPlus, dsNone); TDecisionDrawState = set of TDecisionDrawStates; function CellDrawState(ACol, ARow: Integer; var Value: string; var DrawState: TDecisionDrawState) : boolean; |
Pu
|
Позволяет определить назначение любой ячейки сетки. Параметры ACol и ARow определяют положение ячейки в сетке. В параметре Value возвращается строка, содержащая значение в том виде, как оно представлено в ячейке. Параметр DrawState возвращает информацию о назначении ячейки |
|
function CellValueArray (ACol, ARow: Integer; var ValueArray: TValueArray): boolean;
|
Pu
|
Возвращает индексы всех полей, данные которых суммированы в ячейке. Параметр ValueArray содержит индексы полей |
Для доступа к значению каждой ячейки используется свойство cells. Адресация ячеек осуществляется с левой верхней ячейки, которая имеет индексы [0,0].
Свойство Dimensions является экземпляром объекта TDisplayDims, который инкапсулирует индексированный список указателей на экземпляры объектов TDispiayDim. Каждый такой объект содержит важнейшие визуальные свойства размерностей. При щелчке на кнопке в однострочном редакторе свойства в Инспекторе объектов разворачивается список всех таких объектов.
Свойства и методы компонента TDecisionPivot
Таблица 30.6. Свойства и методы компонента TDecisionPivot
|
Объявление
|
Тип
|
Описание
|
|
Свойства
|
||
|
property DecisionSource: TDecisionSource;
|
Рb
|
Определяет компонент TDecisionSource, через который осуществляется управление многомерным представлением данных |
type TDecisionButtonPosition = (xtHorizontal, xtVertical, XtLeftTop) ;
property GroupLayout: TDecisionButtonPosition; |
Рb
|
Задает способ расположения кнопок на панели: |
|
type
TDecisionPivotOption = (xtRows, xtColumns, xtSuiranaries); TDecisionPivotOptions = set of TDecisionPivotOption; property Groups : TDecisionPivotOptions; |
РЬ
|
Управляет видимостью трех групп кнопок |
|
property GroupSpacing: Integer;
|
Pb
|
Определяет размер в пикселах промежутка между группами кнопок |
|
Методы
|
||
|
procedure SetBounds (Left, Top, Height, Width: Integer); override;
|
Pu
|
Переустанавливает размеры компонента в соответствии с параметрами метода |
Управление данными
Управление данными
Несомненное преимущество многомерного представления данных в том, что пользователь может легко изменить взаимное положение размерностей одной стороны между собой и переносить размерности с горизонтали на вертикаль и обратно. Для того чтобы сделать размерность видимой или невидимой, пользователю достаточно щелкнуть на кнопке.
Взаимное положение и расположение размерностей по сторонам кросстаба никак не связано с местом полей в запросе компонента TDecisionQuery.
Все операции по управлению многомерным представлением сосредоточены в одном компоненте — TDecisionPivot (см. Рисунок 30.2). В некоторой степени это аналог компонента TDBNavigator, только TDecisionPivot управляет не записями набора данных, а размерностями многомерного представления данных.
Этот компонент подключается к общей цепочке компонентов многомерного представления данных через компонент TDecisionSource. Поэтому любые действия с компонентом TDecisionPivot немедленно отражаются во всех визуальных компонентах, которые также подключены к этому экземпляру TDecisionSource.
Взаимосвязь компонентов многомерного представления данных
Взаимосвязь компонентов многомерного представления данных
При создании в приложении формы для многомерного представления данных следует помнить, что при этом обязательно должны решаться следующие задачи:
Для создания запроса SQL можно использовать компонент TDecisionQuery или обычный компонент TQuery.
Запрос должен быть связан с компонентом TDecisionCube, который осуществляет подготовку набора данных запроса к многомерному показу.
Для соединения многомерного набора данных с компонентом отображения данных используется компонент TDecisionSource — полный функциональный аналог TDataSource. Этот компонент, в свою очередь, должен связываться и с набором данных, и с инструментом многомерного представления данных.
Непосредственный показ многомерного набора данных проводится при помощи компонентов TDecisionGrid и TDecisionGraph. Они должны поддерживать соединение с компонентом TDecisionSource.
Наконец, управление многомерным представлением данных реализует компонент TDecisionPivot, он также должен быть связан с компонентом TDecisionSource.
Допустим, что на форме расположены следующие компоненты:
Компоненты Rave Reports и отчеты в приложении Delphi
Диалоговое окно выбора папки, созданное при вызове функции ShBrowseForFolder
Рисунок 31.3. Диалоговое окно выбора папки, созданное при вызове функции ShBrowseForFolder

В данном примере корневой служит виртуальная папка My Computer. Пользователю предоставляется возможность выбрать одну из папок файловой системы (за это отвечает флаг TBrowseinfo.uiFlags, равный
BIF_RETURNONLYFSDIRS).
На выходе функция возвращает pidi папки, имя которой извлекается из него вызовом еще одной функции Shell — shGetPathFromList.
procedure TForml/ButtonlClick(Sender: TObject) ;
var
BI : TBrowselnfo;
Image : integer;
StartPIDL, ResPIDL : PItemlDList;
S, Path : ArraytO..max_path-l] Of WideChar;
begin
01eCheck(SHGetSpecialFolderLocation(Handle, CSIDL_DRIVES, StartPIDL));
With BI do
Begin
hwndOwner = Application.Handle;
pszDisplayName = @S;
IpszTitle = 'Выберите необходимую папку';
ulFlags = BIF_RETURNONLYFSDIRS;
pidlRoot = StartPIDL;
Ipfn = nil;
iImage = 1;
end;
ResPIDL := SHBrowseForFolder(BI) ;
if SHGETPathFromlDList(ResPIDL, @Path[0])
then Labe11.Caption := StrPas(@Path[0]) ;
end;
Полученное имя здесь отображается при помощи компонента Label 1.
3. Наконец, перейдем к третьему действию нашей задачи. Теперь, зная pidi папки, с которой вы будете работать, можно получить указатель на интерфейс ishellFolder вызовом метода BindToObject. Мы еще не рассмотрели такой важный аспект работы с папками, как просмотр их содержимого. Верные правилу СОМ: "каждый должен заниматься своим делом", разработчики Shell предоставили для просмотра еще один интерфейс — IEnumiDList. Пугаться нечего, набор возможностей этого интерфейса даже меньше, чем у пульта ДУ в магнитофоне. Его четыре метода — Next, Skip, Reset и clone — позволяют организовать просмотр списка в одном направлении, а также возврат к началу и дублирование (Clone) выбранного элемента списка. Вот как это выглядит на практике.
Memol.Clear; try
01eCheck(SHGetDesktopFolder(DeskTop));
if not Succeeded(DeskTop.ParseDisplayName
(Self.Handle,nil, StringToWideChar (Editl.Text,ws, MAX_PATH),n, pidi, attr))
then begin ShowMessage('Неизвестное имя');
Exit; end; OleCheck(DeskTop.BindToObject(pidl,nil, IID_IShellFolder, Pointer(NewShellFolder)});
OleCheck(NewShellFolder.EnumObj ects{Self.Handle,
SHCONTF_FOLDERS or SHCONTF_NONFOLDERS, Enumerator)); while Enumerator.Next(1, pidl, Numpidls) = S_OK do
begin
NewShellFolder.GetDisplayNameOf(PIDL, SHGDN_FORPARSING, StrRet); case StrRet.uType of STRRET_CSTR:
s := StrRet.cStr; STRRET_OFFSET:
begin
P := @PIDL.mkid.abID[StrRet.uOffset - SizeOf(PIDL.mkid.cb)];
SetString(s, P, PIDL.mkid.cb - StrRet.uOffset);
end; STRRET_WSTR:
s := StrRet.pOleStr;
end;//case
Memol.Lines.Add(s);
end; except
on ErEOleSysError do ShowMessage('');
end;
В этом примере имя нужной папки извлекается из компонента Edit1. Получив указатель на интерфейс ishellFoider и затем интерфейс IEnumiDList, программа заполняет полученными именами файлов список Memol.Lines.
Помимо названия из большинства объектов файловой системы можно "вытащить" массу полезной информации. Чаще всего задаются вопросом: а как извлечь значок, соответствующий данному файлу или хранящийся в нем?
Способов для достижения этой цели несколько. Самый простой — через вызов функции:
function SHGetFileInfo(pszPath: PAnsiChar; dwFileAttributes: DWORD;
var psfi: TSHFilelnfo; cbFilelnfo, uFlags: UINT): DWORD;
Параметр pszPath может быть указателем как на строку с именем файла, так и на структуру вида pidl. Функция заполняет структуру psfi (тип TSHFilelnfo) длиной cbFilelnfo байт. В зависимости от значения слова флагов (параметр uFlags) на выходе может быть разнообразная информация. В частности, если в параметре uFlags заданы значения SHGFI_SYSICONINDEX и SHGFI_ICON, то в структуру psfi будет записан номер значка для данного файла в системном списке изображений, а результатом выполнения функции будет дескриптор этого списка. Воспользоваться им можно (например, для панели инструментов) так:
procedure TForml.FormCreate(Sender: TObject);
var
Filelnfo: TSHFilelnfo;
ImageListHandle: THandle;
begin
ImageListHandle := SHGetFilelnfo('С:\',
0,
Filelnfo, SizeOf(Filelnfo) ,
SHGFI_SYSICONINDEX or SHGFI_ICON);
SendMessage(ToolBarl.Handle, TB_SETIMAGELIST, 0, ImageListHandle);
end;
Точно так же можно извлечь значок, соответствующий конкретному файлу. В составе Shell есть другие функции, созданные для извлечения значков:
Добавление пунктов в системное контекстное меню
Добавление пунктов в системное контекстное меню
Вы обращали внимание на то, что некоторые приложения после установки добавляют в системное контекстное меню свои собственные пункты? Так поступают многие архиваторы, антивирусные средства и другие утилиты. Эта возможность предоставляется оболочкой Windows.
Когда пользователь щелкает правой кнопкой мыши на любом объекте в пространстве имен, система создает контекстное меню из двух частей: стандартного меню для объектов данного типа и пунктов меню, добавляемых зарегистрированными обработчиками. Зарегистрированные обработчики — это СОМ-серверы, запускаемые в адресном пространстве процесса (in-process servers) и реализованные в виде динамических библиотек.
Ваш СОМ-объект, который расширяет системное контекстное меню, должен поддерживать как минимум два интерфейса — ishellExtinit и IContextMenu. существует и два новых интерфейса — IContextMenu2 и icontextMenuS, но они вносят в логику работы контекстных меню лишь небольшие дополнения и здесь рассмотрены не будут. Интерфейс ishellExtinit отвечает за инициализацию меню, а интерфейс IContextMenu — за выполнение основных функций.
Методы интерфейса IContextMenu приведены в табл. 31.3.
Интерфейс IShellFolder
Интерфейс IShellFolder
Этот интерфейс соответствует папке — одному из основных элементов пространства имен Проводника. Зачем было вводить термин "папка", когда существовали уже общепринятые "каталог" и "директория"? В отличие от последних двух, папка может быть не просто обычным элементом файловой системы. Она может быть виртуальной — как папки Принтеры, Документы или Панель управления. Любая папка может содержать коллекцию объектов из состава пространства имен.
Получив указатель на интерфейс ishellFoider, соответствующий папке, вы можете работать с ней, как с объектом СОМ. "Верхушкой" (корневой папкой) пространства имен является папка Рабочий стол (Desktop). Получить интерфейс isheiiFoider этой папки можно путем вызова функции:
function SHGetDesktopFolder(var ppshf: IShellFolder): HResult;
Логика работы с описываемым интерфейсом такова: сначала необходимо получить интерфейс нужной папки, а затем можно переходить к работе с ее содержимым. Содержимое представляет собой список, а каждый элемент папки представлен структурой pitemiDList. Эта структура не типизирована; ее единственное обязательное поле содержит длину в байтах, зная которую можно переместиться к следующему элементу. То есть получается обычная цепочка. Все остальные поля заполняются соответствующими функциями и методами интерфейса ishellFoider.
Примечание
Примечание
Все служебные функции работы со структурами PitemiDList — создание, уничтожение, копирование, перемещение по цепочке и т. п. — содержатся в примере Virtual ListView, поставляемом с Delphi. Если вы намерены писать программы, работающие с ishellFoider, целесообразно взять их на заметку. В дальнейшем для простоты эти структуры будем именовать pidl.
Рассмотрим функции интерфейса ishellFoider. Под "текущей папкой" в табл. 31.1 понимается та папка, которая в данный момент представляет интерфейс IShellFolder.
Интерфейс IShellLink
Интерфейс IShellLink
Этот интерфейс представляет собой средство для создания и управления ярлыками (shortcuts). Все читатели этой главы наверняка создавали и перемещали ярлыки для наиболее нужных программ, файлов и папок — на рабочем столе, в главном меню и т. д. С точки зрения ОС эти действия — не что иное, как создание и изменение свойств СОМ-объекта.
Каждый ярлык содержит следующую информацию:
IShellLink = interface(lUnknown)
{ si }
[SID_IShellLinkA]
function GetPath(pszFile: PAnsiChar;
cchMaxPath: Integer; var pfd: TWin32FindData;
fFlags: DWORD): HResult; stdcall;
function GetlDList(var ppidl: PItemlDList): HResult; stdcall;
function SetlDList(pidl: PItemlDList): HResult;
stdcall;
function GetDescription(pszName: PAnsiChar;
cchMaxName: Integer): HResult;
stdcall;
function SetDescription(pszName: PAnsiChar): HResult; stdcall;
function GetWorkingDirectory(pszDir: PAnsiChar; cchMaxPath: Integer): HResult; stdcall;
function SetWorkingDirectory(pszDir: PAnsiChar): HResult; stdcall;
function GetArguments(pszArgs: PAnsiChar; cchMaxPath: Integer): HResult; stdcall;
function SetArguments(pszArgs: PAnsiChar): HResult; stdcall;
function GetHotkey(var pwHotkey: Word): HResult; stdcall;
function SetHotkey(wHotkey: Word): HResult; stdcall;
function GetShowCmd(out piShowCmd: Integer): HResult; stdcall;
function SetShowCmd(iShowCmd: Integer): HResult; stdcall;
function GetIconLocation(pszIconPath: PAnsiChar; cchlconPath: Integer; out pilcon: Integer): HResult; stdcall;
function SetlconLocation(pszIconPath: PAnsiChar; ilcon: Integer): HResult; stdcall;
function SetRelativePath(pszPathRel: PAnsiChar; dwReserved: DWORD): HResult; stdcall;
function Resolve(Wnd: HWND; fFlags: DWORD): HResult; stdcall;
function SetPath(pszFile: PAnsiChar): HResult; stdcall;
end;
Сохраним ярлык для данной программы-примера где-нибудь на диске, скажем, в той же самой папке. Для этого создадим новый объект NewLink класса CLSiD_shellLink, предоставляющий нам нужный интерфейс:
procedure TForml.ButtonlClick(Sender: TObject);
var NewLink : IShellLink;
fn, fp : string; ws : WideString; hRes : THandle;
pf : IPersistFile;
begin
NewLink := CreateComObject(CLSID_ShellLink) as IShellLink; fn := ParamStr(O); NewLink.SetPath(pchar(fn));
fp := ExtractFilePath(fn); NewLink.SetWorkingDirectory(pchar(fp)); NewLink.SetDescription
(pChar(Application.Title));
ws := fp+Application.Title+'.Ink';
hRes := NewLink.Querylnterface(IID__IPersistFile, pf) ; if Succeeded(hRes) then
pf.Save(pWideChar(ws),False);
end;
В этом примере помимо IShellLink нужно получить доступ к интерфейсу IPersistFile, который "умеет" записывать данные. Задав параметры ярлыка, мы записываем его на диск. При этом проверяется тот факт, что созданный нами объект поддерживает интерфейс IPersistFile. Если указатель на этот интерфейс получен, вызывается его метод save.
Среди перечисленных выше методов IShellLink особое внимание уделим методу Resolve. Он понадобится вам при получении указателя на интерфейс уже существующих ярлыков. Windows пытается вести себя "разумно" и отслеживает перемещения и переименования объекта, на который указывает существующий IShellLink. Но если вы записали содержимое ярлыка в поток (или на диск), то отследить соответствие ярлыка объекту должны сами, вызвав метод Resolve. Если объект, на который ссылается ярлык, по-прежнему находится на своем месте, метод немедленно завершается с нормальным кодом возврата. Если файл или объект перемещен или переименован, начинается его поиск (Рисунок 31.2).
Над значком, помещенным на панель System Tray, видна строка подсказки
Рисунок 31.1. Над значком, помещенным на панель System Tray, видна строка подсказки

Сообщение, задаваемое в поле uCallbackMessage, по сути дела является единственной ниточкой, связывающей вас со значком после его создания. Оно объединяет в себе несколько сообщений. Когда к вам пришло такое сообщение (в примере, рассмотренном выше, оно имеет идентификатор WM_MYTRAYNOTIFY), поля в переданной в обработчик структуре типа TMessage распределены так. Параметр wParam содержит номер значка (тот самый, что задавался в поле uID при его создании), а параметр LParam — идентификатор сообщения от мыши, вроде WM_MOUSEMOVE, WM_LBUTTONDOWN и т. п. К сожалению, остальная информация из этих сообщений теряется. Координаты мыши в момент события придется узнать, вызвав функцию API GetCursorPos:
procedure TForml.WMICON(var msg: TMessage);
var P : TPoint; begin case msg.LParam of
WM_LBUTTONDOWN:
begin
GetCursorPos(p);
SetForegroundWindow(Application.MainForm.Handle); PopupMenul.Popup(P.X, P.Y);
end;
WM_LBUTTONUP :
end;
end;
Обратите внимание, что при показе всплывающего меню недостаточно просто вызвать метод Popup. При этом нужно вынести главную форму приложения на передний план, в противном случае она не получит сообщений от меню.
Теперь решим еще две задачи. Во-первых, как сделать, чтобы приложение минимизировалось не на Панель задач (TaskBar), а на System Tray? И более того — как сразу запустить его в минимизированном виде, а показывать главную форму только по наступлении определенного события (приходу почты, наступлению определенного времени и т. п.).
Ответ на первый вопрос очевиден. Если минимизировать не только окно главной формы приложения (Application.MainForm.Handle), но и окно приложения (Application.Handle), то приложение полностью исчезнет "с экранов радаров". В этот самый момент нужно создать значок на панели System Tray. В его всплывающем меню должен быть пункт, при выборе которого оба окна восстанавливаются, а значок удаляется.
Чтобы приложение запустилось сразу в минимизированном виде и без главной формы, следует к вышесказанному добавить установку свойства Application.showMainForm в значение False. Здесь возникает одна сложность — если главная форма создавалась в невидимом состоянии, ее компоненты будут также созданы невидимыми. Поэтому при первом ее показе установим их свойство visible в значение True. Чтобы не повторять это дважды, установим флаг — глобальную переменную shownonce:
procedure TForml.HideMainForm;
begin
Appiication.showMainForm := False;
ShowWindow(Application.Handle, SW_HIDE);
ShowWindow(Application.MainForm.Handle, SW_HIDE);
end;
procedure TForml.RestoreMainForm;
var i,j : Integer;
begin
Appiication.showMainForm := True;
ShowWindow(Application.Handle, SW_RESTORE); ShowWindow(Application.MainForm.Handle, SW_RESTORE);
if not ShownOnce then begin
for I := 0 to Application.MainForm.ComponentCount -1 do if Application.MainForm.Components[I] is TWinControl then with Application.MainForm.Components[I] as TWinControl do if Visible then
begin
ShowWindow(Handle, SW_SHOWDEFAULT);
for J := 0 to ComponentCount -1 do if Components[J] is TWinControl then
ShowWindow((Components[J] as TWinControl).Handle, SW_SHOWDEFAULT);
end;
ShownOnce := True;
end;
end;
procedure TForml.WMSYSCOMMAND(var msg: TMessage);
begin inherited;
if (Msg.wParam=SC_MINIMIZE) then
begin
HideMainForm; CreateTraylcon(l) ;
end;
end;
procedure TForml.FileOpenltemlClick(Sender: TObject); begin
RestoreMainForm;
DeleteTraylcon(l);
end;
Теперь у вас в руках полноценный набор средств для работы с панелью System Tray. В заключение необходимо добавить, что все описанное реализуется не в операционной системе, а в оболочке ОС — Проводнике (Explorer). В принципе, и Windows NT 4/2000, и Windows 95/98 допускают замену оболочки ОС на другие, например DashBoard или LightStep. Там функции панели System Tray могут быть не реализованы или реализованы через другие API. Впрочем, случаи замены оболочки достаточно редки.
Поиск объекта, на который указывает ярлык
Рисунок 31.2. Поиск объекта, на который указывает ярлык

Знакомая картина, не правда ли? Особенно часто она наблюдается в том случае, если пользователь не выработал у себя привычки правильно деинсталлировать раздобытый где-то "софт", стирая его "по старинке". Между тем, за привычным диалоговым окном на рисунке стоит вызов метода ishellLink. Resolve. Если в пределах досягаемости поиска окажется файл с тем же именем и размерами, ярлык будет автоматически переадресован на него; в противном случае пользователю будет предложено использовать ближайший по характеристикам файл из просмотренных. Если вы вообще не хотите, чтобы пользователь вмешивался в процесс отыскания соответствия, при вызове метода Resolve в параметре fFlags укажите значение SLR_NO_UI — диалоговое окно появляться в этом случае не будет.
Если вы внимательно изучили рабочий стол своего компьютера, то должны были заметить там ярлыки, ссылающиеся не на файлы, а на специальные объекты — "Мой компьютер", "Сетевое окружение", "Принтеры" и т. п. Чтобы создать такой ярлык самому, нужно обращение к методу setiDList. В качестве параметра ему передается структура pitemiDList (pidi). О том, где ее взять и как заполнить, рассказано в следующем разделе.
Понятие пространства имен
Понятие пространства имен
Необходимость как-то упорядочить все те сущности, с которыми имеет дело современная ОС, всегда вставала перед разработчиками. Довольно успешный подход к этому реализован в платформе Windows. Вооружившись идеями объектного подхода, в Microsoft разбили интерфейс ОС на две части: средства поддержки пространства имен и средства его просмотра.
Под пространством имен оболочки (Shell Namespace) мы будем понимать иерархически упорядоченную совокупность имен всех объектов, которые могут быть просмотрены через средства просмотра — файлы, устройства памяти, принтеры, сетевые ресурсы. В этой совокупности могут встречаться как реально существующие объекты (папки файловой системы), так и виртуальные объекты (папки Принтеры, Мой компьютер и т. п.)- Типовым средством просмотра пространства имен является Explorer (Проводник), но можно заменить его на другое средство, в том числе собственноручно разработанное. Обе составные части являются совокупностями СОМ-объектов, они обладают полиморфизмом и легко расширяемы. Об использовании этих объектов и функций API оболочки ОС и пойдет речь в данной главе.
Размещение значка приложения на System Tray
Размещение значка приложения на System Tray
Часто программисту приходится сталкиваться с задачей написания приложения, работающего в фоновом режиме и не нуждающегося в месте на Панели задач. Если вы посмотрите на правый нижний угол рабочего стола Windows, то наверняка найдете там приложения, для которых эта проблема решена: часы, переключатель раскладок клавиатуры, регулятор громкости и т. п. Ясно, что, как бы вы не увеличивали и не уменьшали формы своего приложения, попасть туда обычным путем не удастся. Способ для этого предоставляет Shell API.
Те картинки, которые находятся на System Tray — это действительно просто картинки, а не свернутые окна. Они управляются и располагаются панелью System Tray. Она же берет на себя еще две функции: показ подсказки для каждого из значков и оповещение приложения, создавшего значок, обо всех перемещениях мыши над ним.
Весь API System Tray состоит из 1 (одной) функции:
function Shell_NotifyIcon(dwMessage: DWORD;
IpData: PNotifylconData): BOOL; PNotifylconData = TNotifylconData; TNotifylconData = record
cbSize: DWORD;
Wnd: HWND;
uID: UINT;
uFlags: UINT;
uCallbackMessage: UINT;
hlcon: HICON;
szTip: array [0..63] of AnsiChar;
end;
Параметр dwMessage определяет одну из операций: NIM_ADD означает добавление значка в область, NIM_DELETE — удаление, NIM_MODIFY — изменение.
Ход операции зависит от того, какие поля структуры TNotifyiconData будут заполнены.
Обязательным для заполнения является поле cbsize — там содержится размер структуры. Поле wnd должно содержать дескриптор окна, которое будет оповещаться о событиях, связанных со значком. Идентификатор сообщения Windows, которое вы хотите получать от системы о перемещениях мыши над значком, запишите в поле uCallbackMessage. Если вы хотите, чтобы при этих перемещениях над вашим значком показывалась подсказка, то задайте ее текст в поле szTip. В поле UID задается номер значка — каждое приложение может поместить на System Tray сколько угодно значков. Дальнейшие операции вы будете производить, задавая этот номер. Дескриптор помещаемого значка должен быть задан в поле hIcon. Здесь вы можете задать значок, связанный с вашим приложением, или загрузить свой — из ресурсов.
Примечание
Примечание
Изменить главный значок приложения можно в диалоговом окне Project/ Options на странице Application. Он будет доступен через свойство Application.Icon. Тут же можно отредактировать и строку для подсказки — свойство Application.Title.
Наконец, в поле uFlags вы должны сообщить системе, что именно вы от нее хотите, или, другими словами, какие из полей hicon, uCaiibackMessage и szTip вы на самом деле заполнили. В этом поле предусмотрена комбинация трех флагов: NIF_ICON, NIF_MESSAGE и NIF_TIP. Вы можете заполнить, скажем, поле szTip, но если вы при этом не установили флаг NIF_TIP, созданный вами значок не будет иметь строки с подсказкой.
Два приведенных ниже метода иллюстрируют сказанное. Первый из них создает значок на System Tray, а второй — уничтожает его.
const WM_MYTRAYNOTIFY = WMJJSER + 123;
procedure TForml.CreateTraylcon(n:Integer);
var nidata : TNotifyiconData;
begin
with nidata do
begin
cbSize := SizeOf{TNotifyiconData) ;
Wnd := Self.Handle;
uID := n;
uFiags := NIF_ICON or NIF_MESSAGE or NIFJTIP;
uCallBackMessage := WM_MYTRAYNOTIFY;
hicon := Application.Icon.Handle;
szTip := 'THis is Traylcon Example';
end;
Shell_NotifyIcon(NIM_ADD, @nidata);
end;
procedure TForml.DeleteTraylcon(n:Integer);
var nidata : TNotifylconData; begin
with nidata do
begin
cbSize := SizeOf(TNotifylconData);
Wnd := Self.Handle; uID := n; end;
Shell_NotifyIcon(NIM_DELETE, @nidata);
end;
Примечание
Примечание
He забывайте уничтожать созданные вами значки на System Tray. Это не делается автоматически даже при закрытии приложения. Значок будет удален только после перезагрузки системы.
Внешний вид значка, помещенного нами на System Tray, ничем не отличается от значков других приложений (Рисунок 31.1).
в этой главе, могут дать
Резюме
Несколько тем, затронутых в этой главе, могут дать лишь начальные представления о принципах работы с оболочкой Windows. Вы можете изучить составляющие ее объекты практически сколь угодно глубоко — были бы потребность да желание. Разумеется, чтобы не "наломать дров", перед этим надо отдать себе отчет в полном и правильном понимании механизмов СОМ.
Функции интерфейса IShellFolder
Таблица 31.1. Функции интерфейса IShellFolder
|
Метод
|
Описание
|
|
function ParseDisplayName (hwndOwner : HWND; pbcReserved: Pointer; IpszDisplayName: POLESTR; out pchEaten: ULONG; out ppidl: PitemiDList; var dwAttributes : ULONG) : HResult;
|
Эта функция позволяет получить указатель на элемент ppidl, зная только его полное имя (с путем) IpszDisplayName |
|
function EnumObjects (hwndOwner: HWND; grf Flags: DWORD; out EnumlDList: lEnumlDList) : HResult;
|
Возвращает указатель на специальный интерфейс lEnumlDList, предназначенный для организации цикла по всем элементам списка в текущей папке |
|
function BindToObject (pidl: PitemiDList; pbcReserved: Pointer; const riid: TIID; out ppvOut: Pointer) : HResult;
|
Возвращает интерфейс папки pidl, которая должна находиться в текущей папке (на которую ссылается интерфейс, вызвавший этот метод) |
|
function ComparelDs (IParam: LPARAM; pidll, pid!2: PitemiDList): HResult;
|
Сравнивает два первых элемента В списках pidll И pidl2 |
function CreateViewObject (hwndOwner: HWND; const riid: TIID; out ppvOut: Pointer) : HResult;
|
Создает визуальный объект для текущей папки и возвращает указатель на него в параметре ppvOut
|
|
function GetAttributesOf (cidl: UINT; var apidl: PItemlDList; var rgflnOut: UINT): HResult;
|
Возвращает атрибуты элемента под номером cidl в списке apidl. Результат — набор флагов, устанавливаемых в параметре rgf inOut
|
|
function GetUIObjectOf (hwndOwner : HWND; cidl: UINT; var apidl: PItemlDList; const riid: TIID; prgflnOut: Pointer; out ppvOut: Pointer) : HResult;
|
Создает объект пользовательского интерфейса, связанный с элементом списка aplidl под номером cidl
|
|
function GetDisplayNameOf (pidl: PItemlDList; uFlags: DWORD; var IpName: TStrRet) : HResult;
|
Возвращает имя элемента pidl. Полнота возвращаемой информации определяется параметром uFlags
|
|
function SetNaraeOf (hwndOwner: HWND; pidl: PItemlDList; IpszName: POLEStr; uFlags: DWORD; var ppidlOut: PItemlDList) : HResult;
|
Задает новое имя IpszName для списка pidl. При этом возвращается новый указатель на список — ppidlOut
|
1. Получить указатель на интерфейс какой-либо папки, скажем, рабочего стола при помощи ShGetDesktopFolder.
2. Получить указатель (pidl) нужного вам элемента. Это осуществимо многими способами. Первый из них — как раз через вызов метода IShellFolder. ParseDisplayName. Если вы хотите получить доступ к одной из виртуальных (специальных) папок, то незаменимой будет следующая функция:
function SHGetSpecialFolderLocation(hwndOwner: HWND; nFolder: Integer; var ppidl: PItemlDList): HResult;
В параметре nFolder вы задаете константу, соответствующую выбранной специальной папке. На выходе будет указатель на элемент ppidl, соответствующий этой папке.
Примечание
Примечание
Во многих функциях Shell API и методах его интерфейсов встречается параметр hwndOwner. Он должен задавать дескриптор окна на тот случай, если придется выводить диалоговое окно или окно с сообщением об ошибке.
Возможные значения параметра nFolder перечислены в табл. 31.2. В комментариях к ним "виртуальная" папка является особым объектом, который предоставляется пользователю при помощи Shell API. Просто "папка" реально существует где-то в файловой системе.
Константы, определяющие специальные папки
Таблица 31.2. Константы, определяющие специальные папки
|
Значение
|
Комментарий
|
|
CSIDL_BITBUCKET
|
Корзина (Recycle bin) — специальная папка для удаленных файлов. Пути к Recycle bin нет в системном реестре во избежание перемещения или удаления, и его не узнать иным методом |
|
CSIDL_CONTROLS
|
Панель инструментов (Control Panel) — виртуальная папка, содержащая значки апплетов Панели инструментов |
|
CSIDL_DESKTOP
|
Виртуальная папка Рабочий стол (Desktop), корневая в пространстве имен |
|
CSIDL_DESKTOPDIRECTORY
|
Папка файловой системы, реально содержащая объекты рабочего стола |
|
CSIDL DRIVES
|
Виртуальная папка Мой компьютер (My Computer), содержащая элементы для всех накопителей на компьютере подключенных сетевых устройств, папки Принтеры, Панель инструментов, Удаленный доступ к сети |
|
CSIDL FONTS
|
Виртуальная папка Шрифты |
|
CSIDL NETHOOD
|
Папка, содержащая объекты сетевого окружения |
|
CSIDL_NETWORK
|
Виртуальная папка Сетевое окружение (Network Neighborhood) |
|
CSIDL_PERSONAL
|
Папка Мои документы |
|
CSIDL_PRINTERS
|
Виртуальная папка Принтеры (Printers) |
|
CSIDL_PROGRAMS
|
Папка Программы из главного меню, содержащая папки установленных на компьютере программ |
|
CSIDL RECENT
|
Папка, содержащая ссылки на последние использовавшиеся документы (Recent) |
|
CSIDL SENDTO
|
Папка, содержащая элементы контекстного меню Send To...
|
CSIDL_STARTMENU
|
Папка, содержащая элементы главного меню Пуск (Start) |
|
CSIDL_STARTUP
|
Папка, содержащая элементы меню Автозапуск (Startup) |
|
CSIDL_TEMPIATES
|
Папка, содержащая шаблоны типовых документов |
function ShBrowseForFolder(var Ipbi: TBrowselnfo): PItemlDList;
Перед ее вызовом следует заполнить структуру типа TBrowselnfo, содержащую в частности pidi того элемента, который будет корневым. После вызова функции пользователь увидит перед собой диалоговое окно выбора папки (Рисунок 31.3).
Методы интерфейса IContextMenu
Таблица 31.3. Методы интерфейса IContextMenu
|
Метод
|
Описание
|
|
function QueryContextMenu (Menu: HMENU; indexMenu, idCmdFirst, idCmdLast, uFlags: UINT) : HResult; stdcall;
|
Добавляет пункт к системному контекстному меню |
|
function InvokeCommand(var- Ipici: TCMInvokeCommandlnfo): HResult; stdcall;
|
Осуществляет вызов обработчика |
|
function GetCommandString (idCmd, uType: UINT; pwReserved: POINT; PszName: LPSTR; cchMax: UINT) : HResult; stdcall;
|
Возвращает описание добавленного пункта меню (подсказку или полное название) |
InsertMenu!
function TContextMenu.QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst,
idCmdLast, uFlags: UINT): HResult;
begin
Result := 0; // или использовать MakeResult(SEVERITY_SUCCESS, // FACILITY_NULL, 0);
if ( (uFlags and $OOOOOOOF) = CMF__NORMAL)
or
((uFlags and CMF_EXPLORE) о 0) then begin
// Добавить один пункт меню во всплывающее меню
InsertMenu(Menu, indexMenu, MF__STRING or MF_BYPOSITION, idCmdFirst,
'Compile...');
Result := 1;
// или использовать MakeResult(SEVERITY_SUCCESS, //
FACILITY_NULL, 1)
end;
end;
Метод Getcornmandstring предоставляет системе данные о пункте меню, в частности, текст подсказки; эта подсказка будет отображаться в строке состояния Проводника, когда курсор находится в нужном месте меню.
Параметры Getcommandstring просты. Первый — idCmd — соответствует идентификатору пункта меню, второй — uType — запрос на тип информации (GCS_HELPTEXT — текст подсказки, GCS_VERB — полное название пункта меню). Наконец, параметры pszName и cchMax задают буфер, в который будут копироваться текстовые данные. Полное название необходимо системе, чтобы с его помощью вызывать предусмотренные в пункте действия программно. В примере ContMenu возврат названия (т. е. обработка запроса GCS_VERB) не предусмотрен, а в ответ на запрос GCS_HELPTEXT возвращается текстовая строка "Compile the selected Delphi project".
Наиболее сложным является метод Invokecommand. Он вызывается при выборе пользователем вставленного вами пункта меню. По сути дела метод InvokeCommand представляет собой прямой аналог обработчика onclick обычных пунктов меню (объектов TMenuitem) в Delphi.
Единственным параметром метода является структура типа TCMinvoke-commandinfo, поля которой имеют такое предназначение:
if (HiWord(Integer(Ipici.IpVerb)) <> 0) then
begin
Exit;
end;
Для создания расширения контекстного меню мы должны породить объект, поддерживающий эти интерфейсы. К сожалению, мастера, предусмотренные в Delphi, не позволяют в автоматизированном режиме создавать объекты, реализующие уже существующие интерфейсы. Поэтому и описание, и реализацию методов придется делать "по старинке", вручную. В примере ContMenu описание объекта таково:
TContextMenu = class(TComObject, IShellExtlnit, IContextMenu) private
FFileName: array[0..MAX_PATHj of Char;
protected
( IShellExtlnit }
function IShellExtlnit.Initialize = SEIInitialize;
function SEIInitialize(pidlFolder: PItemlDList; Ipdobj: IDataObject;
hKeyProgID: HKEY): HResult; stdcall; { IContextMenu }
function QueryContextMenufMenu: HMENU;
indexMenu, idCmdFirst, idCmdLast,
uFlags: UINT): HResult;
stdcall;
function InvokeCommand(var Ipici: TCMInvokeCommandlnfo): HResult; stdcall;
function GetCommandString(idCmd, uType: UINT; pwReserved: POINT;
pszName: LPSTR; cchMax: UINT): HResult;
stdcall;
end;
Вас может насторожить конструкция, описывающая переименование метода initialize интерфейса ishellExtinit. На самом деле одноименный метод имеется у объекта TComObject, и приведенный синтаксис как раз и предназначен для выхода из подобных ситуаций.
Последняя часть работы — регистрация созданного обработчика. Самое подходящее место для этого — метод updateRegistry фабрики класса. Разработчики примера ContMenu породили класс TContextMenuFactory, который при регистрации СОМ-сервера регистрирует создаваемые фабрикой объекты:
Classic := GUIDToString(Class_ContextMenu);
CreateRegKey('DelphiProjectXshellex', '', '')/'
CreateRegKey
('DelphiProject\shellex\ContextMenuHandlers', '', '');
CreateRegKey
('DelphiProject\shellex
\ContextMenuHandlers\ContMenu', '',
ClassID);
Пример ContMenu иллюстрирует "дельфийский" подход к созданию серверов СОМ через соответствующие объекты из иерархии объектов Delphi. Но в папке SHELLEXT вы найдете еще один пример создания расширения для контекстного меню, сделанный целиком и только с использованием интерфейсов и функций СОМ. Присмотритесь к этому примеру внимательнее, если хотите глубже понимать внутреннюю структуру СОМ-объектов.
Программирование: Языки - Технологии - Разработка
- Программирование
- Технологии программирования
- Разработка программ
- Работа с данными
- Методы программирования
- IDE интерфейс
- Графический интерфейс
- Программирование интерфейсов
- Отладка программ
- Тестирование программ
- Программирование на Delphi
- Программирование в ActionScript
- Assembler
- Basic
- Pascal
- Perl
- VBA
- VRML
- XML
- Ada
- Lisp
- Python
- UML
- Форт
- Языки программирования