This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
ББК 32.973-01 Ш83 УДК 681.3.06 Шпак Ю. А. Ш83 Delphi 7 на примерах/Под ред. Ю. С. Ковтанюка — К.: Издательство Юниор, 2003. — 384 с., ил.
ISBN
966-7323-28-5
Книга адресована широкому кругу начинающих разработчиков приложений средствами Delphi 7, предназначенных для управления базами данных разных структур: локальными базами данных и базами данных, расположенными на SQL-серверах. Особое место в книге занимают части, посвященные разработке WEB-серверных и клиентских приложений, а также оптимизации работы с базами данных и их защиты. Изучение материала построено на примерах реальных проектов, которые можно применять в повседневной работе или использовать, как основу, для создания собственных приложений. Кроме того, книга содержит полезную справочную информацию по языку Object Pascal, соглашениям и компонентам Delphi, процедурам и функциям, и основам языка HTML.
ББК 32.973-01 Учебное издание Шпак Юрий Алексеевич
Delphi 7 на примерах Редактор Ю. С. Ковтанюк Подписано в печать 15.04.2003. Формат 70 х 100 1/16. Бумага газетная. Гарнитура Тайме. Печать офсетная. Усл. печ. л. 16,98. Уч.-изд. л. 14,69. Тираж 2000 экз. Заказ № Издательство "Юниор'', Украина, 03142, г. Киев, ул. Совхозная, 35, оф. 1 1 1 тел./ф.: (044) 452-82-22; e-mail: [email protected]; http://www.jimior.com.ua Свидетельство о внесении субъекта издательского дела в Государственный реестр издателей, производителей и распространителей издательской продукции: серия ДК, № 368 от 20 марта 2001 г. 1 hup\ копано ч к н о н и х ,иатшл иши и .ipusiipin 88015. м.Ужюрол. ny.i.Kammana,k-a,l-i5 км ( 0 3 1 2 2 ) 2-96-04 Все названия программных продуктов, устройств и технологий, описанных в данной книге, являются зарегистрированными торговыми марками соответствующих фирм. Все права защищены законодательством Украины и международным законодательством об авторском праве. Никакая часть этой книги ни в каких целях не может быть воспроизведена в любой форме и любыми средствами, будь то электронные и.Ш механические, включая фотокопирование к запись на магнитный носитель, или иные средства копирования или сохранения информации без письменного раирешения издательства. .
Глава 6. Доступ к SQL-серверам из приложений Delphi 7
181
Глава 7. Приложение "Персонал"
198
Глава 8. Организация защиты данных
,
267
Глава 9. Советы по оптимизации работы с базами данных
274
Часть III Печать и отчеты в Delphi 7
281
Глава 10. Печать в приложениях Delphi
282
Глава 11. Отчеты Rave Reports
291
Часть IV Разработка приложений для Web
299
Глава 12. Основы языка HTML
300
Глава 13^Разработка Web-серверных приложений
314
Глава 14. Разработка клиентских приложений
339
Приложение А. Основы синтаксиса языка Object Pascal
341
Приложение Б. Соглашения
;
354
Приложение В. Иерархия классов
359
Приложение Г. Компоненты вкладок Standard, Additional и Win32
361
Приложение Д. Процедуры и функции
364
Оглавление Вступление Часть I Введение в Delphi 7
12 ,
13
Глава 1. Разработка приложений в среде Delphi 7 Установка Delphi 7 Интегрированная среда разработки Delphi 7 Конструктор форм, палитра компонентов и окно Object TreeView Палитра компонентов Работа с компонентами в конструкторе форм Выделение компонентов Перемещение компонентов Выравнивание компонентов Изменение размеров компонентов Удаление компонентов Практикум Инспектор объектов Вкладка Properties Вкладка Events • Настройка инспектора объектов Практикум: создание обработчиков событий и создание первого приложения Структура проекта Delphi 7 Исходные файлы проекта Delphi 7 Главный файл проекта (.dpr) Ключевое слово program Раздел uses Директива $R • Объект Application Пример динамического создания форм Метод Application.Run Файлы программных модулей (.pas) Проблема циклических ссылок Файлы форм (.dfm) Представление формы Пример изменения свойств компонента формы Редактор исходного кода и окно Code Explorer Code Insight '. Вкладка Diagram Окно Code Explorer Настройка редактора исходного кода
Разработка панели инструментов Компонент ImageLJst
53 :
60
Разработка игрового поля
62
Разработка редактора лабиринтов
64
Динамическое создание формы
64
Разработка интерфейса редактора лабиринтов
64
Программный код модуля редактора лабиринтов
70
Рисование структуры лабиринта
70
Процедура проверки корректности структуры лабиринта
76
Обработчик события fmNewMaze.OnClose
79
Обработчик события bbSave.OnClick
80
Программный код основного модуля приложения
83
Загрузка лабиринта в игровое поле и в редактор лабиринтов
84
Реализация игровых функций игры "Лабиринт"
90
Отладка приложений
91
Переход в режим отладки Точки прерывания
92 92
Окна отладчика
93
Часть II Delphi 7 и базы данных
97
Выбор типа базы данных
;
Глава 3. Работа с локальными базами данных.., Доступ к локальным базам данных в Delphi Псевдонимы ВОЕ
формат отображения данных
Приложение "Фотоальбом" Настройка ВОЕ и создание псевдонима Программа SQL Explorer
юо 100
'.
Создание приложения баз данных при помощи мастера Database Form Wizard Анализ новой формы Настройка элементов управления данными
98
101
102 107 107 108
111 112 112
6
Delphi 7 на примерах Программа BDE Administrator Создание псевдонима BDE Постановка задачи Разработка главной формы приложения
113 115 115 116
,
Разработка структуры таблицы фотоальбома Компоненты DataSource и Query
,121 123
Компоненты для редактирования и просмотра данных фотоальбома Программный код главной формы Создание нового альбома Открытие альбома Определение набора категорий ;
124 128 129 132 133
Метод SetContent Обработчик события TDataSource.OnDataChange Создание формы выбора и дополнения фотографий в альбом Разработка формы fmPicture Исходный код модуля формы fmPicture Команды Фотографии >• Переименовать и Фотографии >- Удалить
Глава 4. Основы языка SQL Установка и запуск сервера InterBase 6.5 Приложение IBConsole Подключение к серверу Регистрация существующей базы данных Создание базы данных Команды SQL : Создание базы данных Создание таблиц Удаление таблиц Индексы Добавление данных Команды COMMIT и ROLLBACK Просмотр данных ._.:. .'. Выражения в столбцах Ключевое слово WHERE Объединения Вложенные запросы Операция GROUP BY Ключевое слово HAVING Операция ORDER BY Псевдонимы столбцов Псевдонимы таблиц Изменение данных Удаление строк Подключение к базе данных и отключение от базы данных
Исключения Представления Роли Другие элементы InterBase Домены Внешние функции Курсоры Сценарий создания базы данных staff.gdb
174 176 177 177 177 178 178 179
Глава 6. Доступ к SQL-серверам из приложений Delphi 7 Подключение к серверам баз данных Способы подключения к базе данных Подключение к базе данных при помощи компонента Database Доступ к базам данных при помощи средств dbExpress Однонаправленные наборы данных Разработка приложения "Персонал" Форма fmLogin Подключение к базе данных staff.gdb Компонент SQLConnection Программный код подключения Отображение формы fmLogin перед отображением формы fmMain Сохранение параметров приложения в системном реестре Windows Функции чтения и записи информации системного реестра Обзор компонентов dbExpress Компонент SQLDataSet Извлечение метаданных Компонент SQLMonitor : Компонент SimpleDataSet
Глава 7. Приложение "Персонал" Постановка задачи Модуль данных Компоненты SimpleDataSet ' Компоненты для доступа к хранимым процедурам
Delphi 7 на примерах Компонент sqlqChifdDepts List Компонент sqldsDeleteEmp Создание объектов полей Главная форма Вкладка Справочники
-
201 201 202 203 206
Редактирование таблиц справочников Обработчики событий набора данных sidsDeps Обработчики событий набора данных sidsPoss
209 210 212
Обработчики событий набора данных sidsRegions
213
Вкладка Параметры приложения
215
Вкладка Сотрудники Отображение структуры организации при помощи компонента TreeView
219 222
Структура организации — процедура LoadStructure Выбор данных о сотрудниках —процедура SetSelection
225 230
Контекстные меню и события сетки данных dgrStaff Отображение общих данных о сотруднике .-. Добавление новых сотрудников и редактирование общей информации о сотруднике
233 235 238
Форма fmNew
,
238
Работа с компонентом valeData Загрузка фотографии , Процедура DataForEmployee
241 244 245
Удаление данных о сотруднике Автоматическая вставка данных в таблицу JOBS Форма fmTransfer Изменения процедуры DataForEmployee
250 252 252 255
:
Процедура SetTransfer Работа с таблицами FAMILY и JOBS
256 258
v
Объекты столбцов компонента DBGrid Создание и активизация формы fmData Автоматическая вставка значений в поле EmpID
263 263 264
Организация быстрого поиска сотрудников
265
Глава 8. Организация защиты данных
,267
Работа с пользователями в программе IBConsole
267
Регистрация нового пользователя Изменение данных о пользователе Удаление пользователя Права доступа Роли
268 269 269 .•
269 270
Команды GRANT и REVOKE Команда REVOKE
270 272
Глава 9. Советы по оптимизации работы с базами данных Оптимизация баз данных Совет 1. О первичных и внешних ключах....' Совет 2. О хранимых процедурах Совет 3. О триггерах
,
274 274 274 275 275
Оглавление 9 Совет 4. Об индексах Совет 5. О полях типа varchar Совет 6. О размере страниц в базе данных Оптимизация приложений Совет 1. О транзакциях Совет 2. О команде LIKE Совет 3. О подготовке запросов Совет 4. Об извлечении записей Совет 5. Об удаленном подключении Советб. О метаданных , Совет 7. Об объектах полей Оптимизация работы сети
:
,
Часть Печать и отчеты в Delphi 7
276 276 276 276 277 277 277 278 278 279 279 279
281
Глава 10. Печать в приложениях Delphi Печать форм Печать содержимого компонента RichEdit Печать в текстовом режиме Печать в графическом режиме
282 282 285 286 288
Глава 11. Отчеты Rave Reports
291
Создание отчета для приложения "Персонал" Особенности интерфейса программы Rave Reports
291 295
Часть IV Разработка приложений для Web
299
Глава 12. Основы языка HTML
зоо
Структура указателя URL Основы языка HTML Атрибуты элемента BODY Заголовок HTML-страницы Элементы форматирования текста Гиперссылки Открытие другого документа Переход к закладке Списки Вставка изображений Табличное представление данных Формы
300 301 303 304 305 307 307 307 307 308 309 311
Глава 13. Разработка Web-серверных приложений Компонент WebDispatcher Web-операции Параметр Response Параметр Request Операция /home
^
314 315 316 ,.317 31В 318
10 Delphi 7 на примерах Компонент PageProducer Операция /next Дескрипторы подстановки
'.
Подключение к базе данных Операция/full Компонент DataSetTableProducer Операция /dept Операция /search Компонент DataSetPageProducer Данные о семье Формирование страницы ответа Операция /image ,
-
323 326 326 330 332 333 334 334 337
Глава 14. Разработка клиентских приложений
.....ззэ
Приложение А. Основы синтаксиса языка Object Pascal Общие синтаксические правила Объявления типов, переменных и констант Типы данных Целочисленные типы данных Обобщенные типы Базовые типы Типы чисел с плавающей запятой Обобщенный тип Базовые типы Символьные типы данных Булевы типы данных Перечислимые типы данных Подтипы Строковые типы Множества Массивы Статические массивы Динамические массивы Записи Типизированные файлы Классы : Тип данных Variant Вариантные массивы Операторы Порядок выполнения операторов Функции и процедуры Объявление процедур и функций : Параметры Значения по умолчанию Использование массивов в качестве параметров Перегрузка процедур и функций
Оглавление 1 1 Операторы ветвления Оператор if Оператор case Циклические структуры Оператор for Оператор while...do Оператор repeat...until Выход из циклов и процедур
;
Приложение Б. Соглашения Файлы Компоненты Вкладка Standard Вкладка Additional Вкладка Win32 Вкладка Dialogs Вкладка DataControls Вкладка dbExpress Вкладка Internet Вкладка Rave Вкладка ВОЕ Идентификаторы Оформление исходного кода
Приложение Г. Компоненты вкладок Standard, Additional и Win32....361 Вкладка Standard Вкладка Additional Вкладка Win32
Приложение Д. Процедуры и функции Арифметические Для работы с символами Функции для работы с параметрами приложения Для работы с датами и временем Диалоговые Функции для работы с исключениямиДля работы с файловой системой Для работы с вещественными числами '. Преобразования Для работы с порядковыми значениями Для работы со случайными числами Процедуры для работы с множествами Статистические Для работы с обычными строками санкции для работы со строками PChar Тригонометрические
Вступление Данная книга является практическим руководством по разработке приложений средствами Delphi 7 и рассчитана на читателей, которые никогда не работали с этой средой визуального проектирования. Весь материал книги, в соответствии с тематикой разбит на четыре части: • Часть 1 — Введение в Delphi 7. Состоит из двух глав. • Часть 2 — Delphi 7 и базы данных. Состоит из семи глав. • Часть 3 — Печать и отчеты в Delphi 7. Состоит из двух глав. • Часть 4 — Разработка приложение для Web. Состоит из трех глав. В каждой из частей будет разработано полнофункциональное приложение. В части 1 — игровая программа "Лабиринт". В части 2 и 3 — приложения "Фотоальбом" и "Персонал". В части 4 — серверное и клиентское приложения для организации доступа к базам данных в Web. В среде Delphi используется объектно-ориентированный язык Object Pascal. Эта книга не является учебным пособием по данному вопросу, и потому синтаксис языка Object Pascal будет изучаться по ходу рассмотрения конкретных примеров. Более подробную справочную информацию по этому вопросу можно найти в приложениях. Весь материал книги построен по принципу "от простого к сложному". Теоретические основы и понятия раскрываются постепенно, в процессе рассмотрения примеров, поэтому, если вы любите сразу же приступать к практике, минуя глубины теории, то эта книга — именно для вас. Надеюсь, что это пособие станет хорошим базовым подспорьем для всех, кто стремиться стать профессионалом в области разработки приложений Delphi.
ЧАСТЬ
I
ВВЕДЕНИЕ в DELPHI 7 Borland Delphi 7 — это объектно-ориентированная среда визуального программирования (RAD — Rapid Application Development). Она предназначена для ускоренной разработки высокопроизводительных 32-битных приложений, которые могут работать в среде Windows или Linux. При этом Delphi позволяет свести к минимуму объем вводимого вручную программного кода. В состав Delphi входят средства, необходимые для разработки, тестирования и установки приложений, включая обширную библиотеку компонентов (VCL — Visual Components Library), средства визуального проектирования, шаблоны приложений и форм, а также различные мастеры. Если вы еще не знакомы с такими понятиями, как "компонент" или "форма", то вскоре они станут обыденными словами в вашем лексиконе. Эта часть состоит из двух глав. Первая посвящена теоретическим и практическим основам создания припожений в среде Delphi 7, а вторая содержит описание процесса разработки полнофункционапьной игровой программы "Лабиринт".
ч 1
Глава 1
Разработка приложений в среде Delphi 7 Данная глава посвящена программированию в среде Delphi 7. В ней приводится краткий обзор основных элементов интегрированной среды разработки (IDE — Integrated Development Environment) Delphi 7, а также структуры проекта Delphi. Материал рассматривается на примере простого приложения, которое будет разработано по ходу изложения материала. Кроме того, в примечаниях озаглавленных как "Немного теории", будут конспективно рассмотрены некоторые базовые понятия объектно-ориентированного программирования.
Установка Delphi 7 Процесс установки Delphi 7 зависит от выпуска этого программного пакета. При работе с этой книгой необходимо использовать пакет Delphi Enterprise, так как он содержит все средства, необходимые для работы с базами данных и Webсерверами. Установка Delphi выполняется при помощи специального мастера. 1. Запустите мастер — файл install.exe или файл setup.exe. . 2. После ввода серийного номера и подтверждения согласия с лицензионным соглашением будет предложен один из трех вариантов установки: Typical (Типичная), Compact (Сокращенная) и Custom (Выборочная). Рекомендуется выполнить выборочную установку, .чтобы быть уверенным в установке всех элементов, необходимых для работы с данной книгой. 3. Для того чтобы можно было выполнить все примеры, представленные в книге, обязательно должны быть установлены следующие элементы Delphi 7. • Program Files >• Main Program Files — главные программные файлы. • Program Files >- Sample Programs — примеры программ. • Program Files >- Internet Controls — средства для работы с Internet. « Program Files >- Debug Library Files — файлы, необходимые для работы отладчика. • Program Files >• dbExpress — компоненты, используемые для доступа к базам данных. . Program Files >- Help Files —• справочная документация. • Shared Files >- Image Files — файлы изображений: кнопки, курсоры, пиктограммы и т.д.
Интегрированная среда разработки Delphi 7
15
• Shared Files >• Sample Data Files — примеры баз данных. • Shared Files >• Debugger — отладчик. • BDE — средства доступа к базам данных. • IntraWeb — средства разработки приложений для Web. • Rave Visual Designer — средство разработки отчетов. Остальные элементы Delphi 7 при изучении материала, изложенного в данной книге, не используются. 4. В следующем окне мастера установки оставьте выбор драйверов BDE SQL без изменений. 5. Затем можете отказаться от установки средств VisiBroker/CORBA и клиента InterBase, сбросив соответствующие флажки в двух последующих окнах мастера. 6. Пути для размещения файлов Delphi 7 можете оставить без изменений.
Интегрированная среда разработки Delphi 7 При первом запуске Delphi создается проект с пустой формой, открытый в так называемой интегрированной среде разработки (рис. 1.1). Пусть вас не пугает слово "интегрированная". Оно всего лишь означает, что все средства, необходимые для разработки приложений, используются совместно, то есть объединены. Как видно на рис. 1.1, интегрированная среда разработки состоит из следующих основных частей: • главное меню Delphi; • панель инструментов; • палитра компонентов; • конструктор форм; • редактор исходного кода; • окно Code Explorer; • окно Object TreeView; • инспектор объектов (Object Inspector). ПРИМЕЧАНИЕ Обратите внимание на то, что на рис, 1.1 окно редактора исходного кода было перенесено на передний план. Это было сделано умышленно — для наглядности, так как при создании проекта это окно располагается на заднем плане и его практически не видно. Конечно же, в интегрированной среде Delphi используются и другие средства, однако на данном этапе достаточно будет рассмотреть только перечисленные выше основные элементы. Главное меню Delphi расположено вдоль верхнего края окна интегрированной среды, а панели инструментов по умолчанию находятся в левом верхнем углу. Главное меню и панели инструментов Delphi ничем не отличаются от аналогичных средств других приложений Windows, поэтому подробно рассматриваться не будут. Отметим только, что существует шесть панелей инструментов:
16
Глава 1, Разработка приложений в среде Delphi 7
Standard, View, Debug, Custom, Desktops и Internet,— и для их настройки можно воспользоваться командой View >- Toolbars >- Customize, или выполнить команду Customize контекстного меню панели инструментов. :£l • / • . . . - :1;:;11N™..
Рис. 1.1. Проект с пустой формой, открытый в интегрированной среде разработки Delphi 7 ПРИМЕЧАНИЕ Как и в любой другой программе, используемой в среде Windows, контекстные меню Delphi 7 открываются щелчком правой кнопкой мыши. Особо можно выделить разве что панель инструментов Desktops, которая по умолчанию расположена справа от строки главного меню. С ее помощью можно сохранять различные схемы размещения элементов интегрированной среды разработки, а затем выбирать сохраненные схемы из раскрывающегося списка, расположенного на этой панели инструментов.
Конструктор форм, палитра компонентов и окно Object TreeView В конструкторе форм выполняется разработка пользовательского интерфейса приложения: всех визуальных элементов, с которыми работает пользователь. При создании нового проекта конструктор форм состоит из одного окна с заго-
Конструктор форм, палитра компонентов и окно Object TreeView 17 ловком Forml, в котором могут быть размещены различные элементы из палитры компонентов (рис. 1.2). НЕМНОГО ТЕОРИИ Основополагающим понятием в объектно-ориентированном программировании является класс. Класс (class) — это структура, объединяющая данные и механизмы обработки этих данных. В терминах языка Object Pascal классы также называют типами. По этой причине названия всех классов, используемых в Delphi, в соответствии с принятыми соглашениями о присвоении имен, начинаются с английской буквы т (от слова "Туре" — "тип"). № Соглашения о присвоении имен подробно рассматриваются в приложении Б.
Классы Object Pascal составляют иерархию, в которой корневым элементом является класс TObject. Все остальные классы являются производными от класса TObject. Можно создавать сколько угодно новых классов, производных либо непосредственно от класса TObject, либо от одного из его потомков. Такой механизм, позволяющий создавать новые типы, расширяющие возможности родительских типов, называется наследованием №
Более подробно иерархия классов Object Pascal рассматривается в приложении В.
Экземпляр класса {или типа) называется объектом. Таким образом, можно создать любое количество объектов одного класса. Формы — это объекты класса TForm или другого класса, производного от TForm. Они определяют внешний вид приложений Delphi и содержат различные компоненты
Рис. 1.2. Вид конструктора форм при создании нового проекта В приложении может использоваться более одной формы. При этом в проект можно добавлять формы на основании уже готовых шаблонов (например, форму "О программе..."). Для этого необходимо выбрать один из шаблонов, указанных
18 Глава 1. Разработка приложений в среде Delphi 7 на вкладках Forms и Dialogs диалогового окна New Items. Это диалоговое окно вызывается при помощи команды File >- New >• Other или кнопки New Кетзпанели инструментов Standard. Можно также создать собственный шаблон формы. Для этого необходимо выполнить команду Add to Repository контекстного меню конструктора форм. В результате форма будет вставлена в хранилище шаблонов. Обратите внимание на то, что в окне Object TreeView указан только один элемент — Forml. (рис. 1.3). Это свидетельствует о том, что на форме еще не размещено ни одного компонента. После добавления новых компонентов в проект они будут отображены в виде древовидной иерархической структуры. Рис. 1.3. Окно ПРИМЕЧАНИЕ Object TreeView с Если окно Object TreeView не видимо в интегрированной среде одним элементом разработки, выполните команду View >- Object TreeView или Forml нажмите комбинацию клавиш <Shif t+Alt+Fll>
Палитра компонентов При первом запуске Delphi палитра компонентов расположена радом с панелями инструментов под строкой главного меню. Она представляет собой набор именованных вкладок. На каждой вкладке находится набор компонентов той или иной категории (рис. 1:4). Addiiionall V*32| SwUem.) DataAceessj DataCortrefej dt£nae$s| OataSnaoi BDE 1 ADO | Intefiase) W^GetviCBSJ IntemetllLl
Рис. 1.4. Палитра компонентов Так, первая вкладка палитры компонентов называется Standard и содержит стандартные компоненты, наподобие кнопок, полей ввода и списков, а вторая вкладка под названием Additional содержит дополнительные компоненты, например, кнопка с рисунками, сетка данных и т.д. НЕМНОГО ТЕОРИИ Компонент — это объект, который обладает набором свойств, методов и событий (н* о них речь пойдет чуть позже, в разделе "Инспектор объектов", посвященном работе с инспектором объектов). Компоненты бывают визуальными и невизуальными. Визуальные компоненты могут отображаться на форме во время выполнения приложения. К ним относятся кнопки, поля ввода, списки, сетки данных и т.п. Невизуальные компоненты отображаются на форме только на этапе проектирования в виде пиктограмм. К таким компонентам относятся, например, таймер, и компоненты, предназначенные для организации доступа к базам данных.
Конструктор форм, палитра компонентов и окно Object TreeView
19
Для того чтобы разместить на форме какой-нибудь компонент, необходимо сначала щелкнуть мышью на его пиктограмме в палитре компонентов. При этом пиктограмма станет "вдавленной", подобно нажатой кнопки панели инструментов (рис. 1.5). Затем нужно щелкнуть в конструкторе форм, чтобы выбранный компонент был размещен в соответствующей позиции на форме. JAddlicrall Win32| SvstemS-DataAcqessi DataCorfrofel dbExMessl DalaSnaolBDE j ADS 1 IrWfBase! WebServigetl inlernelLtU.
If" Ц A PET |§| По Д* •' * Ц Щ *** '""; iji" [
.Ш
Рис. 1.5. Компонент Button, выбранный в палитре компонентов Для отмены выбранного компонента в палитре нужно щелкнуть на пиктограмме с изображением стрелки, расположенной слева от пиктограмм компонентов (см. рис. 1.5). ПРИМЕЧАНИЕ Если подвести указатель мыши к одной из пиктограмм компонента и задержать ее в таком положении, на экране появится всплывающая подсказка с названием соответствующего компонента. Такие же подсказки используются и для компонентов, расположенных в конструкторе форм, но в этом случае они содержат кроме названия компонента его координаты на форме, данные о размере и другую полезную вспомогательную информацию. На странице Standard палитры компонентов выберите компонент Button (Кнопка) и, расположите его на форме. Теперь аналогичным образом расположите на форме рядом с первой еще две кнопки (рис. 1.6). Обратите внимание на то, что теперь в окне Object TreeView у объекта Forml появилось три подчиненных объекта Button. ..-, ,-• .
.а ,
T3 Button!
;;:::.:::;
'Buttoni
. .::
Bu!ion2 1 ; ; ;
Buions
•;.:::.:.:':;:
13| Butlon3
Рис. 1.6. Три компонента Button, размещенные на форме Как видите, каждой кнопке было автоматически присвоено стандартное имя Buttonl, Button2 и Buttons. В Delphi всем компонентам, размещаемым на форме, автоматически присваивается имя, состоящее из названия компонента и наименьшего незанятого порядкового номера для данного типа компонентов.
20 • Глава 1. Разработка приложений в среде Delphi 7 Щелкните мышью на кнопке Button2. В результате она будет "выделена" вокруг нее появятся черные маркеры в виде маленьких квадратов. Это означает, что компонент является выбранным в текущий момент времени. Теперь нажмите клавишу , или выполните команду Edit >- Delete контекстного меню объекта Button2. В результате кнопка будет удалена с формы. Расположите на форме еще одну кнопку. Как видите, новой кнопке будет присвоено имя Button2, а не Button4 в соответствии с наименьшим незанятым порядковым номером для данного типа компонентов. Имена, автоматически присвоенные Delphi, можно в дальнейшем изменить при помощи инспектора объектов. » См. подробно в разделе "Инспектор объектов" этой главы.
СОВЕТ Последнее удаление компонента в конструкторе форм можно отменить, нажав комбинацию клавиш или выполнив команду Edit >• Undelete. Для быстрого перехода на одну из вкладок можно выполнить команду Tabs контекстного меню палитры компонентов. Кроме того, при помощи команды Properties контекстного меню палитры компонентов мржно открыть диалоговое окно ее свойств Palette Properties (рис. 1.7). Доступ к редактору свойств палитры компонентов можно также получить, если выполнить команду главного меню Tools >• Environmet Options и перейти в открывшемся диалоговом окне на вкладку Palette. (Palette Pioperlies Piietle Components. Additional Common Controls Win32 System Data Access Data Controls dbEypiess DataSnap BDE ADO Intel Base WebServices Internet Express Internet WebSnap Decision Cube Dialogs Win 31 Add..
|
Name
Package
IT : TMarMenu %
TPopupMenu
A ; TLabel
dclsldTO dclsld70 dclsld70
Г"Ь!П TEd*
defetd7G
jij'i Т Memo
dclstdTO
=Ж1 Т Button
ddstdTO
Qerwme..
Cancel
Рис. 1.7. Диалоговое окно свойств палитры компонентов В диалоговом окне Palette Properties можно добавлять (кнопка Add), удалять (кнопка Delete) и переименовывать (кнопка Rename) вкладки палитры компонентов, а также изменять порядок их следования (кнопки Move Up и
Конструктор форм, палитра компонентов и окно Object TreeView
21
Move Down). Кроме того, можно скрывать (кнопка Hide) и изменять порядок следования компонентов на любой вкладке. Также можно перетащить при помощи мыши компонент с одной вкладки на другую.
Работа с компонентами в конструкторе форм Чтобы понять, как организована работа с компонентами непосредственно в самом конструкторе форм, а также какое место при этом занимает окно Object TreeView, рассмотрим форму, созданную в предыдущем разделе, на которой размещены три кнопки (см. рис. 1.6).
Выделение компонентов Компоненты, выделенные на форме, становятся активными. Это означает, что к ним можно применять действия для модификации их свойств либо непосредственно в конструкторе форм, либо в инспекторе объектов. w См. подробно Б разделе "Инспектор объектов" этой главы.
На этапе проектирования компонент можно сделать активным пятью различными способами. 1. Щелкнув на нем мышью в конструкторе форм. 2. При помощи рамки выделения, получаемой перемещением указателя мыши при нажатой левой кнопке мыши. При этом не обязательно, чтобы компонент был охвачен полностью — достаточно только его частичного пересечения с рамкой выделения. 3. Выделив мышью элемент древовидной структуры в окне Object TreeView. 4. При помощи клавиши <ТаЬ> и клавиш управления курсором с изображением стрелок, позволяющих переходить от компонента к компоненту в конструкторе форм, а также — в окне Object TreeView — при помощи полного набора клавиш управления курсором. 5. Выбрав необходимый компонент в раскрывающемся списке, расположенном в верхней части инспектора объектов. Для того чтобы выделить одновременно несколько компонентов, необходимо выполнить одно из следующих действий. • Воспользоваться рамкой выделения. • Щелкнуть поочередно мышью на каждом из компонентов в конструкторе форм, удерживая нажатой клавишу <shif t>. • Щелкнуть поочередно мышью на каждом из компонентов в окне Object TreeView, удерживая нажатой клавишу <shif t> или клавишу , или воспользоваться клавишами управления курсором при нажатой клавише <shif t>. Для того чтобы выделить все компоненты, можно выполнить команду Edit >• Select All контекстного меню конструктора форм, или выполнить аналогичную команду главного меню Delphi. .
22
Глава 1. Разработка приложений в среде Delphi 7
ПРИМЕЧАНИЕ Если при выделении компонентов в окне Object TreeView нажата клавиша , то в древовидной структуре можно выбирать компоненты в произвольном порядке. Если же нажата клавиша <Shif t>, то в древовидной структуре можно выделить сразу диапазон компонентов — для этого достаточно выделить только первый и последний компоненты требуемого диапазона.
Перемещение компонентов Перемещение компонентов и изменение их размеров можно выполнять либо непосредственно в конструкторе форм, либо при помощи инспектора объектов. Перемещение выделенных компонентов в конструкторе форм выполняется при помощи мыши (удерживая нажатой левую кнопку) или при помощи клавиш управления курсором с изображением стрелок при нажатой клавише . Перемещение компонентов при помощи мыши выполняется либо с учетом привязки к узлам сетки (шаг сетки по умолчанию равен 8 пикселям), либо произвольно, если привязка к сетке отключена. При перемещении компонентов с помощью клавиш управления курсором с изображением стрелок сдвиг выполняется всегда только на один пиксель. ПРИМЕЧАНИЕ Настройка параметров сетки (отображение, привязка, шаг) выполняется на вкладке Designer диалогового окна Environment Options. Для того чтобы открыть это окно, необходимо выполнить одноименную с ним команду главного меню Tools. Параметры сетки устанавливаются в поле Grid Options. Режим привязки к сетке устанавливается флажком Snap to Grid.
Выравнивание
компонентов
Чтобы выровнять активные компоненты по узлам сетки, можно выполнить команду Position >• Align to Grid контекстного меню этих компонентов. Для выравнивания нескольких выделенных компонентов относительно друг друга можно воспользоваться командой Position >- Align контекстного меню выделенных компонентов, и указать в раскрывшемся диалоговом окне Alignment способ их выравнивания по горизонтали и вертикали.
Изменение размеров компонентов Чтобы изменить размеры выделенных компонентов, можно воспользоваться одним из следующих способов. • При помощи перетаскивания мышью маркеров выделения (только для одного выделенного компонента). Обратите внимание, что при размещении указателя мыши над одним из маркеров он изменяет свой вид. Если установлен режим привязки к сетке, то изменение размеров компонента будет выполняться в соответствии с установленным шагом сетки. • При помощи клавиш управления курсором с изображением стрелок при нажатой клавише <Shift>. В этом t случае одно нажатие клавиши приводит к
.
Конструктор форм, палитра компонентов и окно Object TreeView
23
изменению размеров компонент на один пиксель. Этот способ позволяет одновременно изменять размеры сразу нескольких выделенных компонентов. • При помощи команды Position >• Size контекстного меню выделенных компонентов. В диалоговом окне Size можно либо непосредственно указать ширину и высоту выделенных компонентов, либо задать изменение размеров в соответствии с самым маленьким или самым большим из нескольких выделенных компонентов. / • При помощи команды Position >• Scale контекстного меню конструктора форм. Выполнение этой команды приводит к открытию на экране диалогового окна Scale, при помощи которого можно изменять размеры выделенных компонентов в относительных единицах измерения — процентах — так называемое масштабирование всех размещенных на форме компонентов. Например, если указать масштаб 200%, то размеры и координаты размещения всех компонентов будут увеличены в два раза. ПРИМЕЧАНИЕ Команда Scale отличается от команды Size тем, что применяется ко всем размещенным на форме компонентам, а не только к тем, которые выделены в данный момент. Таким образом, понятие "масштабирование" относится, скорее, к форме в целом, чем к отдельным ее компонентам. Это означает, что в случае, если необходимо выполнить команду Scale, для вызова контекстного меню можно щелкнуть правой кнопкой мыши в любом месте формы.
Удаление компонентов Как уже отмечалось ранее, удалить выделенные компоненты можно либо в конструкторе форм при помощи клавиши , либо при помощи команды Delete из меню Edit. Также можно удалить выделенные компоненты в окне Object TreeView при помощи той же клавиши , команды Edit >Delete или одноименной кнопки панели инструментов этого окна.
Практикум 1. Измените размеры кнопок Buttonl, Button2 и Buttons, а также расположите их на форме в произвольном порядке любым из выше описанных методов. 2. Выделите все три кнопки и выполните команду Position >• Size контекстного меню этих кнопок. В поле Width (Ширина), открывшегося диалогового окна, выберите значение Grow to largest (Увеличить до наибольшего), а в поле Height (Высота) — значение Shrink to smallest (Сжать до наименьшего). 3. Еще раз щелкните на выделенных компонентах правой кнопкой мыши и выполните команду Position >• Align их контекстного меню. В открывшемся на экране диалоговом окне выберите в поле Horizontal значение Center in window — центрирование в окне по горизонтали. В поле Vertical — значение Tops — выравнивание по вертикали относительно верхнего края последнего выделенного компонента.
24
Глава 1. Разработка приложений в среде Delphi 7
4. Расположите выделенные компоненты Button вдоль верхнего края формы, например, как показано на рис. 1.8.
ButtonS
Рис. 1.8. Кнопки после изменения их размеров и положения
Инспектор объектов Инспектор объектов (окно Object Inspector) является одним из ключевых элементов интегрированной среды разработки Delphi, так как он позволяет устанавливать значения свойств компонентов на этапе проектирования и определять методы обработки событий. На рис. 1.9 представлен вид инспектора объектов для выделенной кнопки Buttonl. ПРИМЕЧАНИЕ Если окно инспектора объектов не видимо в интегрированной среде разработки, тогда для его отображения на экране воспользуйтесь командой View >- Object Inspector или нажмите клавишу . НЕМНОГО ТЕОРИИ
Свойства (properties) — это характеристики объекта, которые влияют либо на его внешний вид, либо на его поведение. Методы (methods) — это процедуры или функции связанные с класРис. 1.9. Инспектор сом и определяющие поведение объекта. объектов » Определение процедур и функций приводится в приложении А. Для доступа к свойствам и методам в языке Object Pascal используются следующие конструкции: <объект>.<свойство> <объект>.<метод> Также необходимо отметить, что свойства сами могут быть объектами, а следовательно в свою очередь могут иметь свойства и методы. С точки зрения наследования объекты производного класса "наследуют" все свойства и методы родительского класса, а также могут иметь собственные свойства и методы. Рассмотрим это на простом примере.
Инспектор объектов
25
НЕМНОГО ТЕОРИИ Предположим, существует класс ТСаг (автомобиль). Для него определено .свойство TCar.Kind (грузовой или легковой), а также два метода: ТСаг .Start (запустить двигатель) и ТСаг.stop (остановить двигатель). Можно создать класс TFord, производный от класса ТСаг, которому будут соответствовать автомобили "Форд". Благодаря механизму наследования, он будет обладать теми же свойствами и методами, что и родительский класс. Но для нового класса можно определить новые свойства: TFord.Model (модель, например "Scorpio"), TFord. Color (цвет) и т.д. Среди всех методов особое место занимают, так называемые, обработчики событий (event handler). Их название понятно из их названия — они отвечают за обработку событий, происходящих с объектом. События (events) — это действия, обнаруживаемые приложением. По соглашению языка Object Pascal о присвоении имен названия событий начинаются с префикса On. Так, в примере с автомобилем можно определить события ТСаг. OnStart и ТСаг. OnStop, которые возникают в тот момент, когда двигатель автомобиля запускается или останавливается. В конфигурации, выбранной по умолчанию, окно инспектора объектов включает в себя следующие элементы. • Раскрывающийся список, содержащий отсортированный перечень всех размещенных на форме компонентов. Текущим элементом в этом списке является объект, выбранный в данный момент времени. При этом рядом с именем объекта указывается класс, экземпляром которого он является (в нашем примере (см. рис. 1.9) — выбран объект Buttonl, который является экземпляром класса TButton). Если выбрано более одного компонента, то в списке будет отображено количество выбранных объектов. • Вкладка Properties, содержащая перечень свойств выбранного объекта. Если выделено более одного объекта, то будут отображены только те свойства, которые являются для этих объектов общими. • Вкладка Events, содержащая перечень событий выбранного объекта. Если выделено более одного объекта, то будут отображены только те события, которые являются для этих объектов общими. • Строка состояния. В строке состояния отображается количество свойств или событий, сокрытых в результате выполнения одной из команд View контекстного меню инспектора объектов. Если все свойства или события видимы, то в строке состояния отображается фраза "All shown" ("Все показаны"). » Использование команды View контекстного меню инспектора объектов рассматривается в разделе "Настройка инспектора объектов" этой главы.
Вкладка Properties Каждое поле свойства в инспекторе объектов состоит из двух частей: имени и редактора значения. Редактор значения свойства может быть в виде простого текстового поля, в виде раскрывающегося списка (например, для свойств, принимающих значения True или False), а также в виде более сложных объектов.
26
Глава 1. Разработка приложений в среде Delphi 7
Рассмотрим один из редакторов сложных свойств. Для этого выполните следующие действия. • Расположите на .форме, например, представленной на рис. 1.8, компонент Edit (Текстовое поле), находящийся на вкладке Standard палитры компонентов (см. рис. 1.4). • Щелкните мышью на маленькой кнопке с изображением троеточия в поле свойства Font (Шрифт). На экране появится диалоговое окно, в котором можно выбрать шрифт и изменить его свойства (рис. 1.10). • Если рядом слева от имени сложного свойства указан символ "+", то это говорит о том, что значения вложенных свойств можно изменять непосредственно в инспекторе объектов. Для этого можно либо щелкнуть мышью на символе "+", либо дважды щелкнуть на имени свойства (рис. 1.11).
по!
Выбор шрифта ШРИФТ. ДЗДдйВДгИД)
Начертание;
Размер.
1 обычный
6
ОК
яии^^ща ИЕ1;; otM^a j
:. ^ШжзЖ^^л^^^^^^^^ш MS Serif ^ *\ гю1тужирньй в Ч? МТ Extra Small Fcrts .,' полужирный курсив . '$? Symbol System _ Ч? Tahcma *j - . Атрибута
12 14 24
Справка 1 .
лП jj . - •
Образец
; ; Г" Паачежнутья
'QFont' Chariet ""Cok*" "" Height
Size 1 t а^ор символов;
!:
Г£<Й
JEditl
Name Pitch
-rl
J
jj
Properties Jf.venu|
:.
; P Зачеркмутьй
ный
-••
|0biectlir,pectoi
Кириллица
[ BStyte IsBokf (s)talc I fsUndraine ; IsSltkebul
"(TFonlj DEFAULT_CHARSET • clWindowTeKl •11
HSSamSetil IfpDefauit S
;J
™
'False ; ! False i False i False
3
J
;
_^j
tAlrfwwn
Рис. 1.10. Диалоговое окно выбора шрифта
Рис. 1.11. Вложенные свойства сложного свойства Font
• В основном, значения свойств компонентов выбираются из заранее определенных списков. Например, выделите на форме компонент Editl и перейдите в инспекторе объектов в поле свойства Color (Цвет). Для того чтобы изменить значение этого свойства, щелкните мышью на кнопке раскрывающегося списка, и в отобразившемся на экране перечне возможных цветов для данного элемента управления выберите значение clSilver. В результате текстовое поле станет светло-серым.
Вкладка Events На вкладке Events перечислены все события, определенные для выбранного компонента. Выделите на форме (см. рис. 1.8) компонент Buttonl, перейдите в инспектор объектов и дважды щелкните мышью в поле события Onclick. В результате в исходный код приложения будет автоматически добавлена пустая процедура rorml.Buttonlciick. В этой процедуре между ключевыми словами
Инспектор объектов
27
begin и end можно добавить программный код, который будет выполняться по щелчку мышью на кнопке Buttonl. После автоматического создания процедуры обработки события, интегрированная среда разработки переносит фокус в редактор исходного кода, где курсор ввода сразу будет установлен между ключевыми словами процедуры begin и end, а в поле события OnClick инспектора объектов появится имя процедуры ButtonlClick (рис. 1.12). Butlonl PiopMfeg Event* |
Рис. 1.12. Автоматически созданная процедура обработки события Buttonl. Onclick ПРЕДУПРЕЖДЕНИЕ Все автоматически сгенерированные методы, не содержащие никакого программного кода, при компиляции приложения удаляются.
Настройка инспектора объектов Инспектор объектов можно настраивать. Например, можно отсортировать его элементы по категориям или по именам. Для этого нужно выполнить одну команду Arrange >• By Category или команду Arrange >- By Name контекстного меню инспектора объектов. В этом же контекстном меню есть подменю команд View. При помощи этого подменю можно отображать или скрывать свойства и события той или иной категории. Для этого достаточно установить или снять флажок рядом с пунктом, соответствующим категории свойств или событий. Количество сокрытых свойств и событий отображается в строке состояния. • Если выполнить команду View >• АИ контекстного меню инспектора объектов, то будут отображены все свойства и события выделенного компонента.
28
Глава 1. Разработка приложений в среде Delphi 7
• Если выполнить команду View >- None, то все свойства и события выделенного компонента будут сокрыты. • Если же выполнить команду View >- Toggle, то те категории свойств и событий, которые были сокрыты, станут видимы, а те, которые отображались,— будут сокрыты. Для настройки внешнего вида инспектора объектов можно выполнить команду Properties его контекстного меню, или выполнить команду Environment Options из главного меню Tools и в открывшемся диалоговом окне перейти на вкладку Object Inspector.
Практикум: создание обработчиков событий и создание первого приложения 1. Перейдите в инспекторе объектов на вкладку Events. 2. Выберите в раскрывающемся списке, расположенном в верхней части окна Object Inspector, компонент Buttonl (если он еще не выбран) и дважды щелкните мышью в поле события Onclick. 3. Вставьте между ключевыми словами begin и end процедуры Forml.ButtonlClick следующую строку: Editl.Text
:= Buttonl.Caption;
ПРИМЕЧАНИЕ Данный пример является чисто демонстративным, поэтому в нем мы не будем придерживаться соглашений о присвоении имен, тем не менее, при разработке реальных приложений они обязательно будут учтены. № Соглашения о присвоении имен подробно рассматриваются в приложении Б.
Обратите внимание на то, что оператор присваивания имеет вид : =, а не =, а также на то, что в конце оператора языка Object Pascal обязательно должна стоять точка с запятой. В этой строке исходного кода свойству Text (Текст) объекта Editl присваивается значение свойства Caption (Заголовок) объекта Buttonl. Как видите, объектноориентированная модель языка Object Pascal весьма наглядна, и исходный код можно интерпретировать интуитивно. 4. Перейдите в конструктор форм. Для быстрого переключения между конструктором форм и редактором исходного кода можно воспользоваться клавишей . • 5. Выделите компонент Buttonl, перейдите в инспектор объектов. Для быстрого вызова инспектора объектов можно воспользоваться клавишей . 6. На вкладке Properties укажите для свойства caption значение Кнопка 1. Аналогичным образом укажите для свойства Caption компонента Button2 значение Кнопка 2, а для значения Caption компонента Buttons значение Выход.
Инспектор объектов
29
7. Создайте для компонента Buttons обработчик события Onclick и вставьте него, между ключевыми словами begin и end, следующий оператор: Close;
Вызов метода Close для главной формы равнозначен выходу из приложения. 8. Теперь откомпилируйте и запустите созданное приложение. Для этого можно выполнить одно из следующих действий. • Нажать клавишу . • Щелкнуть на кнопке Run панели инструментов Debug с изображением зеленого треугольника (см. рис. 1.1). • Выполнить команду Run >- Run. Если все описанные выше действия были выполнены без ошибок, то приложение будет откомпилировано и запущено (рис. 1.13).
Кнопка 1
Кнопка 2
Выкся
Edill
Рис. 1.13. Внешний вид простого приложения с тремя кнопками и текстовым полем Если в исходном коде будет найдена какая-нибудь ошибка, то соответствующая строка будет выделена, курсор ввода будет указывать на место расположения ошибки, а под редактором исходного кода отобразится сообщение о типе ошибки. СОВЕТ Откомпилировать приложение, не запуская его, можно воспользовавшись комбинацией клавиш . Созданное приложение выполняет следующие действия: • если щелкнуть мышью на кнопке Кнопка 1, то ее название — "Кнопка 1" — будет отображено в текстовом поле; • если щелкнуть мышью на кнопке Кнопка 2, то ничего не произойдет, так как для этой кнопки обработчик события Onclick определен не был; • щелчок мыши на кнопке Выход приведет к завершению работы приложения, приложение будет закрыто с возвратом в интегрированную среду разработки Delphi.
30
Глава 1. Разработка приложений в среде Delphi 7
Теперь определим обработчик события onclick для кнопки Button2. 9. В конструкторе форм выделите кнопку Button2, и перейдите на вкладку Events инспектора объектов. 10. Создавать новый обработчик события при помощи двойного щелчка мыши мы не будем. Вместо этого щелкните мышью в поле OnClick на расположенной справа кнопке раскрывающегося списка и выберите в этом списке ссылку на процедуру Buttoniciick. Это еще один способ определения обработчиков событий для объектов, при помощи которого событию объекта можно назначить процедуру обработки, определенную ранее для другого объекта. В рассматриваемом примере после запуска приложения и щелчка на кнопке Кнопка 2, в текстовом поле все равно будет отображаться надпись "Кнопка 1". П. Давайте несколько видоизменим процедуру Buttoniciick. Для быстрого перехода к исходному коду этой процедуры дважды щелкните мышью в поле Onclick инспектора объектов при выделенной кнопке Buttonl или Button2. 12. Измените оператор, указанный между ключевыми словами begin и end, на приведенный ниже: Editl.Text := TButton(Sender).Caption; НЕМНОГО ТЕОРИИ В этом операторе строке TButton (Sender) соответствует приведение типа TObject параметра Sender к типу TButton. Параметр Sender передается во все обработчики событий. Ему соответствует объект, который стал источником обрабатываемого события, В рассматриваемом примере в качестве такого объекта может выступать либо компонент Buttonl, либо компонент Button2. Так как в качестве компонента-отправителя события (от английского слова "sender" — "отправитель") может выступать объект любого класса, параметр Sender имеет базовый тип TObject. Как уже упоминалось выше, класс TObject находится в основании иерархии классов Object Pascal, и все остальные классы являются производными от него. Тем не менее, в данном примере нельзя вызывать свойство Caption непосредственно для объекта Sender, так как такое свойство в классе TObject не определено. Именно для этого и используется механизм приведения типов, благодаря которому объект Sender может рассматриваться как экземпляр класса TButton. Для приведения типов используется следующая конструкция: <Тип приведения>(<Объект>) Приведение типов в обратном направлении является излишним, так как, благодаря механизму наследования, все свойства и методы родительских классов доступны производным классам. Другими словами, вместо вызова TObject (Buttonl) .ClassName можно написать проще: Buttonl. ClassName. Метод TObject .ClassName возвращает название класса в виде строки, то есть в данном случае будет возвращена строка "TButton". Теперь представленный выше оператор одинаково применим как для кнопки Buttonl, так и для кнопки Button2. Если в качестве объекта Sender выступает кнопка Buttonl, то в текстовом поле будет отображена строка "Кнопка 1"1 если
Структура проекта Delphi 7
31
же в качестве объекта Sender выступает кнопка Button2, то в текстовом поле будет отображена строка "Кнопка 2". Для того чтобы удостовериться в этом, запустите приложение и щелкните поочередно мышью на первых двух кнопках. Как видите, приведение типов позволяет вместо двух процедур обработки события Onclick использовать только одну.
Структура проекта Delphi 7 Прежде, чем рассматривать работу в редакторе исходного кода и окно Code Explorer, кратко изучим структуру проекта Delphi 7 и содержание входящих в него файлов. В Delphi 7 можно создавать проекты различного вида. Если выполнить команду File х New >• Other, то на экране раскроется диалоговое окно New Items (рис. 1.14).
InlraWeb'
j
WebServices
New .„1 ActiveX
\
j
МиМЬг
Business j
ProjecM
j |
WebSnap Fwmt
]
]
Web Documents
Dialogs j
.Piojects |
j
CoAa
Data Modules
Batch File
CLX Application
Component
Console Application
Contra» Panel Application
Conlml Рале! Module
Data Module
DLL Wijard
Form
Frame
Package
Project Group
Resource DLL Wizard
Seivic*
ServiceAppIcation
Text
Thread Object
Unit
Web Server Applic alien
XML Data Binding
г :•...--
с OK
Cancel
.Цедр
Рис, 1.14. Диалоговое окно New Items
При помощи этого окна можно создавать обычные приложения Windows, новые компоненты, консольные приложения, пакеты компонентов, новые формы, DLL-библиотеки и др. В этом разделе будет рассмотрена структура проекта обычного приложения Windows, представляющего собой выполняемый ехе-файл. Этому типу проектов соответствует пиктограмма Application, расположенная на вкладке New диалогового окна New Items (см. рис. 1.14).
32
Глава 1. Разработка приложений в среде Delphi 7
Исходные файлы проекта Delphi 7 В состав проекта обычного приложения Windows, разрабатываемого в Delphi 7, обязательно входят файлы со следующими расширениями. • .dpr — главный файл проекта. Содержит исходный код инициализации приложения и создания форм. • .pas — файлы программных модулей, содержащих исходный код на языке Object Pascal. Для каждой формы, входящей в состав проекта, создается отдельный программный модуль. В исходном тексте такого модуля содержится объявление класса формы. При размещении на форме компонентов конструктор форм Delphi автоматически вносит необходимые изменения в объявление класса этой формы. То же самое происходит и при создании обработчиков событий. Таким образом, Delphi выполняет большой объем "черновой работы" по модификации исходного кода класса формы, позволяя разработчику сосредоточиться только на создании программного кода, определяющего функциональность формы. • .dfm — файлы форм. Практически все операции, выполняемые в конструкторе форм при визуальной разработке, записываются в файл формы соответствующего программного модуля. В этот файл помещаются конкретные значения свойств формы и ее компонентов, в отличие от самого модуля, в котором хранятся только объявления элементов формы и их программный код. • . res — файл ресурсов проекта. Содержит пиктограмму, которая затем сохраняется в выполняемом файле приложения, и другие ресурсы. После сохранения и компиляции проекта, в одном каталоге с ним создается еще несколько файлов. Они являются вспомогательными, и поэтому в данной книге рассматриваться не будут. ПРИМЕЧАНИЕ Файлы, расширение которых начинается с символа "~" (тильда), являются резервными копиями основных файлов. Эти копии соответствуют последнему сохранению внесенных в проект изменений. Рассмотрим структуру проекта Delphi 7 на примере простого -приложения, содержащего три кнопки и текстовое поле, разработанного в предыдущих разделах.
Главный файл проекта (.dpr) Содержимое главного файла проекта Projectl.dpr представлено в листинге 1.1. Для того чтобы открыть этот файл в редакторе исходного кода, необходимо выполнить команду Project >- View Source. Листинг 1.1.Главный файл проекта Projectl.dpr program Projectl; uses Forms ,
1
Структура проекта Delphi 7
33
Окончание листинга 1.1 Unitl in ' U n i t l . p a s '
{Forml};
{$R * . r e s }
begin • Application.Initialize; Application.CreateForm{TForitil, Forml) ; Application.Run; end.
л
.
ПРЕДУПРЕЖД ЕНИ E За исключением редких случаев, непосредственно редактировать этот файл не требуется, так как при компиляции проекта это может привести к ошибкам. Кроме того, существуют некоторые ограничения для имен проектов и программных модулей: они должны начинаться с буквы и состоять только из букв, цифр и символов подчеркивания. Точки и пробелы в таких именах не допускаются.
Ключевое слово program Ключевое слово program в первой строке файла проекта сообщает компилятору о том, что данная программа предназначена для создания выполняемого файла. После него указывается имя проекта (в данном случае — Projectl).
Раздел uses В разделе uses перечисляются программные модули Object Pascal, которые система Delphi должна скомпоновать с данной программой при создании выполняемого файла. Модуль Forms, с которого в листинге 1.1 начинается перечень модулей проекта, — это одна из составных частей библиотеки визуальных компонентов (Visual Component Library — VCL) Delphi. Назначение этого модуля, как видно из его названия, состоит в определении характеристик форм в приложениях, разрабатываемых в Delphi. Остальным модулям (файл проекта из листинга 1.1 содержит только один модуль — Unitl) соответствуют формы, созданные для конкретного проекта. Здесь указывается имя модуля (Unitl), имя файла с исходным кодом этого модуля (Unitl.pas), а также имя формы, хранящейся в данном модуле (Forml). Имени формы соответствует значение свойства Name каждого объекта класса TForm в том виде, в котором оно отображается в инспекторе объектов Delphi, Директива $R Директива $R сообщает компилятору, что в проект нужно включить ресурс Windows, указанный в директиве. Символ звездочки, указанный в качестве имени файла, означает, что имя файла ресурса совпадает с именем файла проекта. Файл ресурсов (.res) создается автоматически при компиляции проекта. Объект Application Объект Application — это экземпляр класса TApplication. Он создается автоматически и предназначен для управления приложением в целом. Инициа-
34
Глава 1. Разработка приложений в среде Delphi 7
лизация приложения начинается с вызова метода Application.Initialize. После него последовательно для каждой формы вызывается метод Application.CreateForm. Этот метод "создает" формы, то есть загружает их в оперативную память. Если в проекте используется несколько форм, то непосредственно при инициализации приложения обязательно необходимо создавать только ту форму, которая является главной. Остальные формы можно создавать динамически, то есть в процессе работы приложения. Такой подход позволяет более рационально использовать системные ресурсы и ускоряет запуск приложений (особенно больших). Рассмотрим пример динамического создания форм. Пример динамического создания форм 1. Откройте в Delphi проект Projectl, если он еще не открыт, и выполните команду File >• New Form, или щелкните мышью на одноименной кнопке панели инструментов View. В конструкторе форм будет создана новая форма Form2. 2. Нажмите клавишу , чтобы перейти в редактор исходного кода проекта. 3. В редакторе исходного кода перейдите на вкладку Projectl, выполнив команду Project >• View Source. В главный файл проекта будет добавлена ссылка на модуль Unit2, а вслед за формой Forml, при инициализации приложения, будет создаваться форма Form2. 4. Для того чтобы отменить создание формы Form2 на этапе инициализации приложения, командой Project >• Options раскройте диалоговое окно, и перейдите в нем на вкладку Forms, как показано на рис. 1.15. iPioiccl Options tor Pfoiectl.enc D sector Jes/Condfonals F Him Application |
jjatntm ^uto-cre Form! Form?
Version Into Compile1
j
IJT =-
Packages
Compiler Messages
^
Linker
г
Vvatobleifflffi*:.
"
;
—.• :.-
.
-
d
,
Г
Е53.
;
.
OK
Cancel
j
;
ildp
Рис. 1.15. Перечень форм, создаваемых при инициализации приложения 5. Дважды щелкните мышью-на форме Form2 или щелкните на кнопке >, чтобы перенести эту форму в перечень доступных форм, не создаваемых автоматически — список Available forms.
Структура проекта Delphi 7
35
6. Щелкните мышью на кнопке ОК. Если теперь просмотреть исходный код главного файла проекта, то можно увидеть, что метод CreateForm вызывается только для формы Forml. 7. Выполните команду File v Save или щелкните мышью на одноименной кнопке панели инструментов Standard, и сохраните файл модуля Unit2 .раз в одном каталоге с остальными файлами проекта. 8. Перейдите в редакторе исходного кода проекта на вкладку Unltl. Добавьте в операторе uses модуля Unitl ссылку на модуль Unit2: uses
Windows, . . . , Unit2; 9. Если этого не сделать, то модуль Unitl не будет "видеть" модуль Unit2. Теперь нажмите клавишу , чтобы перейти в конструктор форм. ПРИМЕЧАНИЕ Для просмотра списка форм можно выполнить одно из следующих действий: • воспользоваться командой View >• Forms; r-g • щелкнуть мышью на кнопке View Form панели инструментов View; Щ • нажать комбинацию клавиш <Shif t+Fl2>. Для просмотра списка программных модулей можно выполнить одно из следующих действий: • воспользоваться командой View >• Units; • щелкнуть мышью на кнопке View Unit панели инструментов View; • нажать комбинацию клавиш . Если после этого в диалоговом окне View Form выделить один из элементов списка, и щелкнуть мышью на кнопке ОК или просто дважды щелкнуть мышью на нужном элементе списка, то будет открыта соответствующая форма. Если тоже самое выполнить в диалоговом окне View Unit — будет открыт соответствующий модуль. 10. Выделите кнопку Button2 и перейдите в инспекторе объектов на вкладку Events. И. Щелкните мышью в поле события OnClick и удалите в нем ссылку на процедуру ButtonlClick. 12. Дважды щелкните мышью в поле события Onclick. В результате будет создана процедура Button2Click. Вставьте между ключевыми словами begin и end этой процедуры следующий программный код: Application.CreateForm(TForra2, Form2); Form2.ShowModal; Form2.Free;
Процедура Button2Click выполняет следующие действия. Первая строка предназначена для создания формы Form2. Вторая строка отображает эту форму при помощи метода ShowModal.
36
Глава 1. Разработка приложений в среде Delphi 7
НЕМНОГО ТЕОРИИ Для отображения форм используется два метода: Show и ShowModal. Они отличаются тем, что после вызова метода Show приложение продолжает выполнять программный код вызывающего модуля, а в случае вызова метода ShowModal управление передается вызываемой форме. В случае использования метода Show можно переключаться между вызывающей и вызываемой формой и производить в них некоторые действия, а в случае использования метода ShowModal вызываемая форма "блокирует" работу приложения до тех пор, пока она не будет закрыта. В качестве примера отображения на экране формы при помощи метода Show можно привести известную форму поиска информации (), используемую в приложениях Microsoft Office. В качестве примера форм, отображаемых при помощи метода ShowModal, можно привести диалоговые окна, подобные Open file и Save file. •
Последняя строка процедуры Button2Click вызывается для формы Form2 метод Free. Этот метод удаляет форму из памяти, освобождая выделенные для нее системные ресурсы. Если бы вместо метода ShowModal в данном примере использовался метод Show, то форма Form2 не отображалась, так как она удалялась бы сразу же после создания метод Free. Благодаря использованию метода ShowModal, форма Form2 удаляется из памяти только после того, как будет закрыта пользователем.
13. Перейдите в конструкторе форм к форме Form2, и уменьшите ее размеры. 14. Запустите приложение и щелкните мышью на кнопке Кнопка 2. В результате будет создана и отображена на экране форма Form2 (рис. 1.16). -101 xl Кнопка 2
f/ Foim2
Рис. 1.16. Динамическое создание формы и ее отображение при помощи метода ShowModal 15. Закройте форму Form2 и выйдите из приложения, например, щелчком мыши на кнопке Выход. Метод Application. Run Для возврата к содержимому главного файла проекта, перейдите на вкладку Project! редактора исходного кода проекта. С метода Application.Run, собственно говоря, и начинается работа приложения. Он запускает цикл обработки
Структура проекта Delphi 7
37
сообщений, на котором построена работа программ. Приложение завершает работу при поступлении сообщения о его закрытии.
Файлы программных модулей (.pas) Рассмотрим структуру файлов программных модулей на примере модуля Unitl, представленного в листинге 1.2. Листинг 1.2. Содержимое программного модуля Unitl .pas unit Unitl; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls/ Unit2; type TForml = class(TForm) Button2: TButton; ( Buttons: TButton; Buttonl: TButton; Editl: TEdit; procedure ButtonlClick(Sender: TObject); procedure ButtonSClick(Sender: TObject); procedure Button2Click(Sender: TObject); private Private declarations } un • public . { Public declarations } end; ' ' var Forml: TForml; .
procedure TForml.Button3Click(Sender: TObject); begin Close; end;
procedure TForml.Button2Click{Sender: TObject); begin Appli ca tion.CreateForm(TForm2, Form2) ; Form2.ShowModal; Form2.Free; end; end.
В этом листинге полужирным шрифтом выделены те фрагменты, которые должны быть вставлены вручную. ПРИМЕЧАНИЕ Перенос строк в тексте программ на языке Object Pascal допускается только после разделительных знаков {точка, запятая, точка с запятой, двоеточие), операторов (:=, =, -, + и т.д), закрывающих скобок и зарезервированных слов (begin, end, then, else, or, and и т.д.). Как видно из листинга 1.2, программный модуль состоит из двух разделов, обозначенных ключевыми словами interface и implementation. В интерфейсном разделе (между ключевыми словами interface и implementation) указываются элементы программного модуля, доступные для других модулей и программ. Здесь объявляются функции, процедуры, переменные, константы и типы. Интерфейсный раздел является функциональным эквивалентом заголовочных файлов языка C++, однако в языке Object Pascal для хранения такой информации не используются отдельные файлы. В данном примере в интерфейсном разделе указаны ссылки на подключаемые модули (включая модуль формы Form2 -- Unit2), а также автоматически созданные Delphi объявления класса формы Forml. Обратите внимание на объявление переменной экземпляра формы: var Forml: TForml;
Здесь объявляется переменная Forml типа TForml. Тип TForml — это созданный при помощи конструктора форм Delphi класс, производный от класса TForm. Переменная Forml инициализируется оператором Application.CreateForm, который был рассмотрен выше. Так как эта переменная объявлена в интерфейсном разделе, то к ней можно обращаться и модифицировать ее значение из
Структура проекта Delphi 7
39
главного файла проекта, а также из других модулей. Для того чтобы можно было обращаться к переменной Forml из других модулей, в разделе uses этих модулей должна быть указана ссылка на модуль Unitl. В разделе реализации, после ключевого слова implementation, располагается программный код модуля. Все операторы, помещаемые в раздел реализации, доступны только внутри данного программного модуля. Для того чтобы получить доступ к тем или иным элементам извне модуля, необходимо поместить соответствующие объявления в интерфейсный раздел этого модуля. Типичным случаем организации программного модуля является размещение объявления функции в разделе interface, а операторов это функции — в разделе implementation.
Проблема циклических ссылок Предположим необходимо, чтобы не только модуль Unitl мог обращаться к модулю Unit2, но и модуль Unit2 мог обращаться к модулю Unitl. В этом случае логично предположить, что в разделе uses интерфейсной части модуля Unit2 необходимо указать ссылку на модуль Unitl. Однако, при попытке компиляции такого модуля Unit2 будет выдано сообщение об ошибке: Circular unit reference to ' U n i t 2 ' По-русски это звучит так: "Циклическая ссылка на модуль Unit2". В большинстве случаев для того, чтобы избежать этой ошибки, достаточно указать оператор uses со ссылкой на модуль Unitl в раздел реализации: implementation uses Unitl;
Файлы форм (.dfm) В файле .dfm описывается каждый компонент формы, включая значения всех свойств. Просмотреть или отредактировать файл формы можно в редакторе исходного кода. Для приложений CLX, предназначенных для использования в среде Linux, используются файлы с расширением .xfm. Представление формы Перейдите в конструкторе форм к форме Forml, щелкните на ней правой кнопкой мыши, и выполните команду View as Text раскрывшегося контекстного меню. В результате будет получено текстовое представление формы Forml, приведенное в листинге 1.3. Листинг 1.3. Содержимое файла Unitl .dfm obj ect Forml: TForml Left = 192 Top = 107 Width = 696 Height = 480 Caption = ' F o r m l '
40
Глава 1. Разработка приложений в среде Delphi 7
Продолжение листинга 1.3 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'MS Sans Serif Font.Style = [] OldCreateOrder = False PixelsPerlnch = 96 TextHeight = 13 object Button2: TButton Left =250 Top = 16 Width =163 Height = 42 Caption = 'Кнопка 2" TabOrder = 0 OnClick = Button2Click end obj ect Button3: TButton Left = 450 Top = 1 6 Width =163 Height = 42 Caption = 'Выход' TabOrder = 1 OnClick = Button3Click end object Buttonl: TButton Left = 58 Top = 1 6 Width = 163 Height = 42 1 Caption = "Кнопка I TabOrder = 2 OnClick = ButtonlClick end object Editl: TEdit Left = 56 Top = 72 Width = 121 Height = 21
Структура проекта Delphi 7
41
Окончание листинга 1.3 Color = clSilver TabOrder = 3 Text = ' E d i t l 1 end end.
Здесь можно изменить любые свойства самой формы и расположенных на ней компонентов. Для возврата в обычный режим работы с формой щелкните правой кнопкой мыши в редакторе исходного кода и выполните команду View as Form раскрывшегося контекстного меню.
Пример изменения свойств компонента формы Описанным выше способом можно отредактировать свойства любого компонента, например, компонента Editl. для этого выполним следующие действия. 1. Выделите компонент Editl и выполните команду Edit >• Cut (Вырезать), или нажмите комбинацию клавиш . В результате компонент Editl будет удален из формы и помещен в буфер обмена. 2. Перейдите в редактор исходного кода, переместитесь в самый конец файла, расположите курсор после последнего ключевого слова end. и выполните команду Edit >- Paste (Вставка), или нажмите комбинацию клавиш . В результате компонент Editl будет вставлен в файл исходного кода в текстовом виде, как показано на рис. 1.17. 3. Измените значение свойства Text на ' V 4. Выделите фрагмент исходного кода, соответствующий компоненту Editl, вырежьте его в буфер обмена — . В Unill.pas 1 j Piojedl j Uniu j I Forms.ShowHodal; I For»2.Free; lend; lend.
I
i ! |
object Editl: TEdli Left = 24
Top - 56 Width - 121 Height - 21 TabOcder = 3 Text - ' E d i t l 1
: end
57: 1,
Рис. 1.17. Текстовое представление компонента Editl
42
Глава 1. Разработка приложений в среде Delphi 7
5. Перейдите в конструктор форм и выполните вставку из буфера обмена . В результате текстовое поле будет пустым. Формы сохраняются в текстовом или двоичном форматах. Выбор формата определяется флажком New forms as text в поле Module creation options на вкладке Designer диалогового окна Environment Options. Для того чтобы раскрыть на экране окно Environment Options необходимо выполнить команду Tools >- Environment options.
Редактор исходного кода и окно Code Explorer Как уже отмечалось выше, при размещении компонентов в конструкторе форм и определении новых обработчиков событий при помощи инспектора объектов интегрированная среда Delphi сама создает соответствующий программный код на языке Object Pascal. В дальнейшем в этот автоматически сгенерированный код можно вносить изменения при помощи встроенного в Delphi полнофункционального редактора, некоторые возможности которого уже были рассмотрены. Внешний вид этого редактора представлен на рис. 1.18.
Рис. 1.18. Редактор исходного кода с окном Code Explorer
Code Insight Одним из самых мощных средств редактора исходного кода Delphi 7 является анализатор кода -- Code Insight. Функции анализатора кода перечислены в табл. 1.1. При помощи кнопок со стрелками, расположенными в правом верхнем углу редактора, можно перемещаться между теми местами в исходном коде, в которых вносились последние изменения. Также можно перемещаться между объявлением процедуры и ее реализацией при помощи комбинаций клавиш и .
Редактор исходного кода и окно Code Explorer
43
Таблица 1.1. Функции Code Insight Название функции
Описание
Завершение кода
Наберите имя класса или объекта с точкой (например, Buttoni.). В результате будет отображен перечень свойств, методов и событий, определенных для этого класса (рис. 1.19). Выберите один из элементов списка и нажмите клавишу <Enter>.
Введите первую часть оператора присваивания (например, Editl.Text := ) и нажмите комбинацию клавиш <съг1+пробел>. В результате будет отображен перечень возможных значений для завершения оператора. Введите начальные символы имени функции, процедуры или метода (например, round) и нажмите комбинацию клавиш . В результате будет отображен перечень возможных продолжений имени (рис. 1.20). A p p l i c a t i o n . CreateFormtTFoemZ, Госкй) ; Form2 . Shouflodal;
Рис. 1.19. Завершение программного кода средствами Code Insight
funclion
RoundRect[DC: HOC; XV IrtegajYV Integer
funchen
Roundto[ci*isiAVakw Doubte;constADigil Т
Рис. 1.20. Подсказка Code Insight о возможных продолжениях имени
Продолжение таблицы 1.1 Название функции
Описание
Подсказка о параметрах
Введите имя метода (например, Application. CreatePorm) и открывающую скобку "("• В результате будет отображена подсказка о наборе параметров метода (рис. 1.21).
Рис. 1.21. Подсказка Code Insight о параметрах метода •
44
Глава 1. Разработка приложений в среде Delphi 7
Окончание таблицы 1.1 Название функции
Описание
Подсказка о Если на этапе отладки задержать указатель мыши над именем значении пере- некоторой переменной, то будет выдана подсказка о текущем менных на эта- значении этой переменной. пе отладки » Отладка приложений Delphi рассматривается в следующей главе. Переход к объ- Если расположить указатель мыши над именем метода, класса, явлению или объекта или переменной, удерживая при этом нажатой клавишу , то он примет форму руки с вытянутым указательреализации ным пальцем, а имя примет форму ссылки, как в HTMLстранице (рис. 1.22). После щелчка мышью на такой ссылке в редакторе исходного кода будет выполнен переход к объявлению свойства, класса или переменной или к реализации метода, функции или процедуры. Шаблоны кода
Если нажать комбинацию клавиш , то будет отображен перечень распространенных структур языка Object Pascal, которые можно вставить в исходный код модуля (рис. 1.23).
Рис. 1.22. Ссылки для перехода к объявлению или реализации
Аррlicetion.СгеасеГогю(ТГогю2, Foc№2); Foriti2 .ShovHodal; Forma.Free; I ray deviation |eon;M aciay declaration (var) case slalement [with else] case slalement class declaration (with Create/Destroy override:] | das 5 declaration (no parti)
Рис. 1.23. Шаблоны структур языка Object Pascal
Вкладка Diagram Вдоль нижнего края окна редактора исходного кода может располагаться одна или несколько вкладок (в зависимости от версии Delphi). В Delphi 7 Enterprise их две: Code и Diagram. На вкладке Code выполняется написание программного кода. На вкладке Diagram отображаются пиктограммы и соединительные линии, показывающие взаимосвязи между размещенными на форме компонентами и их свойствами. Для того чтобы создать диаграмму, перейдите на вкладку Diagram и просто перетащите на нее мышью один или несколько элементов из окна Object TreeView. Если перетаскивать компоненты, имеющие между собой "родственные" связи, то между ними автоматически создаются соединительные линии. Например, если перетащить на вкладку Diagram сначала объект Forml, а затем -
Редактор исходного кода и окно Code Explorer
45
объект Buttonl, то будет автоматически создана соединительная линия со стрелкой, указывающей на "родителя" (рис. 1.24). jlUmH.pas UflM | РюясП 1 JJnKl \ i) Q Valables/Corslanls j juntitiedl ti 2У Uses Unbtlcdl : Hesdiption
«
sail
I
! -Buttrail
. да 3
IModfiri
Рис. 1.24. Диаграмма, показывающая взаимосвязь между объектом Formi и объектом Buttonl
При помощи кнопок панели управления, расположенных вдоль верхнего края вкладки Diagram, можно самостоятельно создавать взаимосвязи между различными элементами диаграммы. Например, выберите соединительную линию типа Property, щелкните мышью на элементе диаграммы Formi и, удерживая нажатой левую кнопку мыши, проведите соединительную линию к элементу диаграммы Buttonl. В результате будет показано, что взаимосвязь между соответствующими объектами организована по свойству Formi.ActiveControl (рис. 1.25). В проекте можно создать более одной диаграммы. При этом каждой из них можно присвоить название (поле Name) и составить их описание (поле Description). При помощи команды File >- Print можно распечатать диаграмму.
Окно Code Explorer При первом запуске Delphi 7 окно Code Explorer располагается вдоль левого края окна редактора исходного кода, как показано на рис. 1.18. ПРИМЕЧАНИЕ Если окно Code Explorer скрыто, выполните команду View >- Code Explorer.
В этом окне отображается древовидная диаграмма исходного кода, открытого в редакторе. Здесь показаны типы, классы, свойства, методы, глобальные переменные и процедуры, определенные в программном модуле. Кроме того, в окне Code Explorer перечислены ссылки на другие модули, указанные в разделе uses. « См. подробнее об операторе uses в разделе "Файлы программных модулей (-pas)" этой главы.
Рис. 1.25. Соединительная линия типа Property между элементами диаграммы Forml и Buttonl При помощи диаграммы Code Explorer можно перемещаться по исходному коду. Например, если дважды щелкнуть мышью на каком-либо методе в окне Code Explorer (например, Button2Click), то курсор в исходном коде переместится к реализации этого метода. Если же дважды щелкнуть мышью на какомлибо объекте (например, Buttonl), то курсор переместится к объявлению этого объекта (рис. 1.26). В Uriitl IMS
(Jr*2
ф BuffSnlOickl Ф Butlon2 * Button2Click [ , О Bullon3 ; 4н ButtonXlick.(: i-S^ Edit! £j Vaiiables/Constants |; CJ Uses
Рис. 1.26. Перемещение по исходному коду при помощи диаграммы в окне Code Explorer Настройка окна Code Explorer выполняется на вкладке Explorer в диалоговом окне Environment Options, раскрываемом одноименной командой из меню Tools.
Что дальше?
Настройка редактора исходного кода
47
-
Для настройки редактора исходного кода предназначено диалоговое окно Edit Properties (рис. 1.27), которое можно раскрыть на экране при помощи команды Tools >• Editor Options. На вкладке General выполняются общие настройки редактора: переход в режим вставки по нажатию клавиши , выделение строки по двойному щелчку мыши и т.д. EdiliH Properties G«i«el I Sou»Options) Display | KejiMappngs] Color j Code Insight] 1
Edto options •••"
F? irwerfJtiode. ЦУ CUTSOI iNoughiab;
Г" ЁЙ1£Я legular expressions I~ ei?ln' blocks
Г
Сигм
Г" Eoubtecfcfcine f" fotcecut andcpey enabled
- Pieswve Ire ends
Jv Аи1о-согг|йе1е ttxl to find EdiaSpeedSetting: DefaLrtkejmapping Undo|n* - 32767
Cancel
Рис. 1.27. Настройка редактора исходного кода
На вкладке Source Options выполняются настройки, характерные для исходного кода, написанного на определенном языке программирования (C/C++, С#, HTML, IDL, Pascal, SQL или XML): расширения файлов, использование табуляторов, отображение символов табуляции и пробелов, ширина табуляции и т.п. На вкладке Display выполняется настройка отображения исходного кода: создание резервных файлов, масштабирование во весь экран, параметры полей, шрифт и т.п. На вкладке Key Mappings определяется рабочий набор клавиатурных эквивалентов команд, на вкладке Color выполняются цветовые настройки редактора, а на последней вкладке — настройка анализатора кода Code Insight.
Что дальше? Итак, в этой главе были рассмотрены основы работы в интегрированной среде разработки и структура проекта Delphi 7. На базе рассмотренного материала в следующей главе будет разработано полнофункциональное игровое приложение "Лабиринт". Это приложение является учебным примером, позволяющим приобрести необходимый навык работы со средствами интегрированной среды разработки.
•
Глава 2
Приложение "Лабиринт" Эта глава полностью посвящена разработке достаточно простого игрового приложения "Лабиринт". Функционально это приложение должно состоять из двух частей: редактора лабиринтов и непосредственно игрового поля. Каждая часть будет реализована в отдельной форме. Таким образом, приложение "Лабиринт будет состоять из двух программных модулей, соответствующих формам лабиринтов и игрового поля. Для создания приложений можно использовать различные мастеры, но в этой главе будет представлен весь процесс создания приложения от пустой формы до полнофункционального приложения. Такой подход позволит детально разобраться в работе со средствами интегрированной среды разработки Delphi. Кроме того, в конце этой главы будут рассмотрены основы использования встроенных средств отладки Delphi 7.
Создание нового приложения 1. Для того чтобы создать новое приложение, выполните команду File >• New >Application. В результате в интегрированной среде разработки будет создан проект, состоящий из одной пустой формы Forml. 2. Для того чтобы сохранить основной файл проекта и файл программного модуля, выполните команду File >- Save All или нажмите комбинацию клавиш <Shift+Ctrl+S>. Можете сохранить проект либо в каталоге, выбранном Delphi по умолчанию, либо каталоге на свое усмотрение. 3. Присвойте файлу программного модуля имя, например, MazeMain.pas, a файлу проекта — Maze.dpr (от англ, слова "maze" "лабиринт"). Теперь название проекта будет отображаться в строке заголовка Delphi.
Параметры проекта Прежде, чем перейти непосредственно к программированию, давайте рассмотрим такой немаловажный вопрос как параметры приложения. Настройка этих параметров выполняется в окне Project Options, доступ к которому можно получить при помощи команды Project >• Options или комбинации клавиш .
Внизу окна Project Options расположен флажок Default. Если этот флажок установить, то все настройки параметров проекта будут рассматриваться как параметры по умолчанию для каждого вновь создаваемого проекта.
Создание нового приложения
49
В предыдущей .главе уже рассматривалась первая вкладка окна Project Options — Forms. На ней можно указать, какая форма приложения является основной, а также составить перечень форм, которые при инициализации приложения должны создаваться автоматически (список Auto-create forms), и которые могут создаваться динамически в процессе выполнения программы (список Available forms). Мы еще вернемся к этой вкладке при разработке приложения "Лабиринт", а сейчас кратко рассмотрим основные элементы остальных вкладок окна Project Options.
, Вкладка Application
На вкладке Application (рис. 2.1) устанавливаются параметры, имеющие отношение непосредственно к самому приложению. Piciiecl Option» lor Маге.еке ii bieetOHes/CorKBionalc Forms
Appfeaion
't Application selling!
|
j
" VetscRlnfo
CoropBa
;,-:-.:
' ' '."-•
Help file: ... leaf
|
Package*
j. Cwrplm Messages
-.-.'•',
|
Linker
• .. -
growse. i,oadlcofi.
Рис. 2.1. Вкладка Application окна Project Options В поле Title указывается строка, отображаемая рядом с пиктограммой приложения, когда оно находится в свернутом виде. В поле Help file указывается имя файла интерактивной справки, связанного с данным приложением (файл с расширением .hip). Для поиска файла на диске можно щелкнуть мышью на кнопке Browse. ПРИМЕЧАНИЕ Для создания файлов справки используются специальные средства. В частности, вместе с Delphi 7 поставляете программа Help Workshop (hcw.exe), которая по умолчанию устанавливается в каталог . . \Delphi7\Help\Toois. В поле Icon указывается пиктограмма, хранимая вместе с выполняемым файлом. Для того чтобы сменить пиктограмму, необходимо щелкнуть мышью на кнопке Load Icon, и выбрать подходящий файл в формате .ico.
•
50
Глава 2. Приложение "Лабиринт"
Вкладка Compiler Параметры, перечисленные на вкладке Compiler (рис. 2.2), оказывают влияние на процесс компиляции и отладки приложения, а также на размер и производительность конечного выполняемого файла. Project Option* lot Магв.ехв Oirectoiies/Cundiiionab
asbnSnfo • |
Fomt. | Apcfcefeonr;
Packages ' ' Urft:
Code generate - -
Г" r
Г Peflfctfr-iale №iv
Г™ Д«вЯо« checkhg
Г? l.oca! 4"nbofe (7 Reference info Г" JJfflrjJeie toofeEKi eval p Ejdended syntax
W DeHnilicnsw^ Г
SfteD**uaDCUt
П? Open B*"nel«s F? Hugesliings ,f~ Д(О[учф1в typed corstaris
Рис. 2.2. Вкладка Compiler окна Project Options Некоторые из этих параметров следует устанавливать только на этапе разработки и отладки, а при завершающей компиляции отключать. В представленной ниже таблице перечислены некоторые параметры компилятора и их использование во время промежуточной и завершающей компиляции. СОВЕТ Те параметры, которые в таблице не указаны, можно отключить. Таблица 2.1. Параметры комптятора Параметр
Описание
Промежуточная компиляция
Завершающая компиляция
Optimization
Оптимизация компиляции. Генерирование Delphi-кода, который обнаруживает неверные команды деления чисел с плавающей запятой. Проверка выхода индекса за пределы массива или строки. Проверка ошибок вывода/вывода после каждой операции ввода/вывода.
Да Да
Да Нет
Да
Нет
Да
Нет
Pentium-safe FDIV Range checking I/O checking
.
Создание нового приложения
51
Окончание таблицы 2.1 Параметр
Описание
'
Промежу-
Завершаю-
T/MJu^a илиТОЧНаЯ КОМ-
И* аа ЩаЯ
ПИЛЯЦИЯ
КОМПИЛЯЦИЯ
Overflow checking
Проверка на переполнение при Да выполнении операций с целыми числами.
Нет
Strict var- string
Проверка ошибок для строковых параметров.
Да
Нет
Extended syntax
Позволяет совершать вызовы Да функций как процедур, и игнорировать возвращаемый результат. Также обеспечивает поддержку строкового типа Pchar.
Да
-
Huge strings
Если этот флажок установлен, Да тогда переменным типа string соответствуют строки очень большой длины (до 2 Гбайт), в противном случае длина таких строк ограничена 256 символами.
Да
Debug information
Размещение в откомпилированном Да модуле отладочной информации.
Нет
Local Symbols
Генерирование дополнительной Да отладочной информации.
Нет
Reference Info/Definitions Only
Генерирование специальной от- Да ладочной информации, необходимой для работы таких средств интегрированной среды разработки как Code Browser и Code Explorer. Если флажок Local symbols не установлен, то эти два параметра бесполезны.
Нет
.
.
Assertions
Приводит к генерированию кода Да проверки семантических элементов программных выражений.
.
Нет
Вкладка Compiler Messages На этой вкладке можно указать виды сообщений, выводимых при компиляции. Например, можно отключить вывод подсказок (флажок Show hints) и предупреждений (флажок Show warnings). Однако, рекомендую оставить эти параметры включенными на этапе разработки и отладки приложений, так как под-
52
Глава 2. Приложение "Лабиринт"
сказки и предупреждения позволяют оптимизировать исходный код и избежать логических программных ошибок. Перед выполнением завершающей компиляции эти параметры лучше отключить.
Вкладка Linker На вкладке Linker перечислены параметры компоновщика. Здесь все можно оставить без изменений. Единственный параметр, на котором можно остановиться — это Linker output (Формат компоновочных файлов). Иногда вместо компоновочных файлов Object Pascal с расширениями .dcu необходимо генерировать объектные файлы в формате C/C++ (файлы .obj). В этом случае нужно выбрать соответствующий переключатель.
Вкладка
Directories/Conditionals
На этой вкладке можно указывать различные рабочие каталоги и условные зависимости. По умолчанию все рабочие каталоги совпадают с каталогом, в котором размещены исходные файлы проекта. При разработке приложений, рассматриваемых в данной книге, можете указать другие каталоги для хранения результирующих исходных файлов (поле Output directory) и откомпилированных компоновочных файлов (поле Unit output directory). Также в поле Search path можно указать пути поиска дополнительных подключаемых модулей, указываемых в разделе uses. « См. подробнее об операторе uses в разделе "Файлы программных модулей (-pas)" главы 1. .
Вкладка Version Info
Если на вкладке Version Info (рис. 2.3) установить флажки Include version information in project (Включить в проект информацию о версии) и Autoincrement build number (Автоматический подсчет количества компиляций), то количество компиляций будет автоматически подсчитываться и сохраняться вместе с приложением. Таблица со столбцами Key (Ключ) и Value (Значение), расположенная в нижней части вкладки Version Info, позволяет указать информацию об авторских правах, торговой марке, версии и другие сведения о приложении. Эта информация отображается при выборе вкладки Version (Версия) диалогового окна File Properties (Свойства файла). Всю эту информацию в дальнейшем можно использовать для организации контроля за версиями, а также выводить в диалоговом окне О программе...
Вкладка Packages На этой вкладке указываются подключения пакетов, содержащих различные наборы компонентов. Пакеты и разработку компонентов мы рассматривать не будем, так как эти вопросы выходят за тематические рамки данной книги.
Создание нового приложения
53
j
f ioiec( Gplion-. F«ms
|
Appfcatmv \
s
СапцЛя '1
|- Lrkw
Packages
, ; Of ed
(7 include aemon nftmaSoain ptopct Majot version
:
MhotariB • '
:
-НмЙвв«г 1
Pnvrfeiwlcl
Betea»
-|укргынек.ий
ileDescnplion :
teVersicri
-1.0.0.0
ntetnalName
Г
Orfeufc
С«тсе1
Рис. 2.З. Вкладка Version Info окна Project Options
Настройка свойств форм Итак, приступим непосредственно к процессу разработки. В простейшем варианте приложение "Лабиринт" должно выполнять три функции: • прохождение уже существующего лабиринта; , • редактирование уже существующего лабиринта; • создание нового лабиринта. Суть игры заключается, конечно же, в прохождении лабиринтов, поэтому соответствующая функциональная часть приложения будет реализована на основной форме, а функции создания и редактирования — на дополнительной форме. Давайте вначале создадим пустые заготовки этих двух форм, а затем добавим в программные модули необходимые методы и процедуры обработки событий. Основная форма уже была создана ранее — ей соответствует модуль с именем MazeMain.pas (а также файл формы MazeMain.dfm). Для того чтобы добавить в проект еще одну форму, выполните следующие действия. 1. Выполните команду File >~ New >~ Form или щелкните мышью в панели инструментов View на кнопке New Form. В результате к проекту будет добавлена пустая форма, которой автоматически будет присвоено имя Form2. 2. Сохраните новый модуль при помощи команды File >- Save, комбинации клавиш или щелчка мышью на одноименной кнопке панели инструментов Standard. В качестве имени файла нового программного модуля укажите MazeNew.pas. ПРИМЕЧАНИЕ Как уже упоминалось в первой главе, для того чтобы быстро перейти к какому-нибудь программному модулю/форме, можно воспользоваться окном View Unit/View Form.
54
Глава 2. Приложение "Лабиринт"
ПРИМЕЧАНИЕ Окна View Unit/View Form раскрываются при помощи соответствующих команд меню View, или комбинаций клавиш и <Shif t+F12>, а также при помощи кнопок View Unit и View Form панели инструментов View. Для того чтобы удалить модуль/форму из проекта, необходимо выполнить команду Project >- Remove from Project, выделить в раскрывшемся на экране списке удаляемые элементы и щелкнуть мышью на кнопке ОК. Для отображения окна Remove from Project можно также воспользоваться кнопкой Remove file from project панели инструментов Standard (с изображением знака "минус" на фоне открытой папки). Теперь проект состоит уже из двух форм, и между ними необходимо установить взаимосвязь. Но для начала изменим при помощи инспектора объектов некоторые свойства форм. 3. Перейдите в конструкторе форм к форме Forml, и откройте инспектор объектов (например, при помощи клавиши ), если он не видим. ПРИМЕЧАНИЕ
•
Находясь в конструкторе форм, можно также открыть инспектор объектов для текущего выбранного компонента, нажав клавишу <Enter>. 4! В инспекторе объектов перейдите на вкладку Properties и присвойте свойству Name основной формы вместо значения Forml значение fmMain. В данном случае выбор имени формы обусловлен соглашениями о присвоении имен. № Соглашения о присвоении имен подробно рассматриваются в приложении Б.
Здесь можно только отметить, что, согласно этим соглашениям, име'на объектов начинаются с нескольких маленьких букв, соответствующих типу этих объектов (в данном случае fm — сокращение от Form), а затем с большой буквы следует вторая часть имени, указывающая на логическую суть объекта (в данном случае — Main, что переводится с английского как "основная"). 5. В представленной ниже таблице перечислены остальные свойства формы fmMain, которые необходимо изменить при помощи инспектора объектов. Таблица 2.2. Свойства формы fmMain Свойство
Новое значение
Пояснение
Caption
Лабиринт
Position
poDefau.lt
WindowState
wsMaximized
Заголовок формы. Размещение формы на экране при запуске приложения выбирается в соответствии с типом формы. При запуске приложения форма разворачивается на весь экран.
6. Перейдите в конструкторе форм к форме Form2, которая будет использоваться для создания и редактирования лабиринтов. Измените ее свойства в соответствии с табл. 2.3.
Создание нового приложения
55
Таблица 2.3. Свойства формы Form2 Свойство
Новое значение
Пояснение Высота формы. Имя формы (от английского слово "new" — означает "новый", a "maze" — "лабиринт"). При запуске приложения форма размещается по центру экрана.
Height
480
Name
f mNewMa z e
Position
poScreenCenter
Width
550
Ширина формы.
7. Форма fmNewMaze может создаваться динамически в процессе работы приложения, поэтому ее можно исключить из списка форм, создаваемых автоматически. Для этого выполните команду Project >• Options и в окне, появившемся на экране, перейдите на вкладку Forms. 8. Выделите в списке Auto-create forms элемент fmNewMaze, а затем щелкните мышью на кнопке с изображением символа ">". В результате форма fmNewMaze будет перенесена в список доступных, но автоматически не создаваемых форм (рис. 2.4). 9. И последнее, что нужно сделать на этом этапе разработки, — добавить ссылку на модуль MazeNew в разделе uses модуля MainMain: uses
Windows,
MazeNew;
10. Сохраните все сделанные в проекте изменения по команде File >- Save All. iPtoiecl Options foi Maze exe Veisbn Wp
fmMair
j Packages .j essages ii*K I
Available lorms; tmWewMaze
Cancel
Рис. 2.4. Форма fmNewMaze будет создаваться динамически
56
Глава 2. Приложение "Лабиринт"
Меню и панель инструментов формы fmMain Создадим строку меню и панель инструментов формы fmMain. 1. Перейдите в конструкторе форм к форме fmMain. 2. Щелкните мышью сначала в. палитре компонентов (см. рис. 1.5) на компоненте MainMenu, расположенном на вкладке Standard, а затем — на форме fmMain. В результате компонент главного меню будет размещен на форме, а его свойства отображены в инспекторе объектов (рис. 2.5). / Лвбиринт
Рис. 2.5. Компонент MainMenu, размещенный на форме fmMain 3. Присвойте свойству Name компонента MainMenu значение mmMain. 4. Теперь необходимо добавить в меню команды. Для этого дважды щелкните мышью в конструкторе форм на компоненте mmMain, в результате чего на экране появится редактор меню (рис. 2.6). if ImMdin.mmMain
Рис. 2.6. Редактор меню fmMain.mmMain
Разработка меню Главное меню приложения "Лабиринт" будет состоять всего из одного меню Лабиринт, которое будет содержать четыре команды: .Создать, Открыть, Отредактировать и Выход.
Создание нового приложения
57
Для того чтобы создать новое меню выполните следующие действия. 1. Выделите в редакторе меню пустой прямоугольник и перейдите в инспектор объектов. Этому прямоугольнику будет соответствовать безымянный объект типа TMmenuItem. 2. Введите в поле свойства Caption значение Лабиринт и нажмите клавишу <Entar>, В результате появится соответствующее меню. 3. Присвойте в инспекторе объектов свойству Name этого меню значение mmiMaze. Теперь добавим в меню Лабиринт четыре команды. 4. Щелкните мышью в редакторе меню на только что созданном меню Лабиринт. 5. Затем нажмите на клавиатуре клавишу со стрелкой вниз или просто щелкните мышью на пустой строке меню, расположенной сразу под названием Лабиринт. В результате в инспекторе объектов опять будет отображен набор свойств безымянного объекта типа TMenultem. 6. Присвойте свойству Caption значение Создать, а свойству Name — значение mmiNewMaze. В меню Лабиринт появится команда Создать, как показано на рис. 2.7. •*' Immain.mmMain
Рис. 2.7. Разработка меню
7. Аналогичным образом создайте в меню Лабиринт команды Открыть (свойство Name имеет значение ramiOpenMaze), Отредактировать (свойство Name имеет значение mmiEditMaze) и Выход (свойство Name имеет значение mmiExit). И в завершение создания меню Лабиринт отделим команду Выход от остальных команд этого меню при помощи специального разделителя. 8. Для этого выделите в редакторе меню команду Выход и нажмите клавишу . В результате в меню будет вставлена новая пустая строка. 9. Перейдите в инспектор объектов, присвойте свойству Caption этого нового объекта значение — "—" (знак "минус") и--нажмите клавишу <Enter>. Перед командой Выход появится разделяющая полоса.
58
Глава 2. Приложение "Лабиринт"
Быстрые клавиши Обычно в приложениях для быстрого выполнения команд им назначают комбинации клавиш, так называемые клавиатурные эквиваленты команд или "быстрые клавиши". Команде Выход обычно соответствуют комбинации клавиш или . Пусть в данном случае будет использоваться комбинация клавиш . Для того чтобы поставить ее в соответствие команду Выход выполните следующие действия. 10. Выделите эту команду в редакторе меню и перейдите в инспектор объектов. П. Найдите свойство Shortcut, укажите для него значение Alt+X и нажмите клавишу <Enter>. Рядом с командой Выход появится ссылка на соответствующую ей комбинацию клавиш быстрого доступа (рис. 2.8). и" fmMain пнлМзт
Рис. 2.8. Меню Лабиринт Обратите внимание, что для свойства Shortcut уже определен набор комбинаций клавиш, который можно просмотреть в поле этого свойства при помощи раскрывающегося списка. Однако, комбинация клавиш в этом списке отсутствует, поэтому ее пришлось указать вручную. 12. Теперь можно закрыть окно редактора меню и определить обработчик события Onclick для команды Выход. Обработчики событий для меню и команд основного меню можно быстро создавать в конструкторе форм. Находясь в конструкторе форм, выполните команду Лабиринт >• Выход, в результате чего в редакторе исходного кода будет создан пустой обработчик события rraniExit.OnClick. 13. Введите между ключевыми словами begin и end оператор Close;. 14. Сохраните проект и запустите приложение, нажав клавишу или щелкнув мышью на кнопке Run в панели инструментов Debug. Испытайте только что созданное меню, а.затем закройте приложение.
Разработка панели инструментов Обычно кнопки панели инструментов дублируют команды меню (хотя это не обязательное условие). Не станет исключением и приложение ''Лабиринт".
Создание нового приложения
59
Создадим панель инструментов, содержащую три кнопки, соответствующие трем первым командам меню Лабиринт. Для команды Выход кнопку создавать не будем. 1. Расположите на форме компонент ToolBar, который находится в палитре компонентов (см. рис. 1.4} на вкладке Win32. 2. Присвойте его свойству Name значение tlbMain. 3. В составное свойство EdgeBorders добавьте значение ebBottom, чтобы панель инструментов отделялась рамкой не только сверху, но и снизу. 4. Щелкните на созданной панели инструментов правой кнопкой мыши и выполните команду New Button контекстного меню, раскрывшегося на экране. В результате на панели инструментов появится кнопка (объект типа TToolButton). 5. Присвойте свойствам этой кнопки значения в соответствии с табл. 2.4. Таблица 2.4. Свойства первой кнопки панели инструментов приложения "Лабиринт" Свойство
Новое значение
Пояснение
Hint
Создать ринт
Menultem
iraniNewMaze
Name
tlbtnNewMaze
Текст всплывающей подсказки, которая появляется, если на кнопке задержать указатель мыши. Это значение можно выбрать из раскрывающегося списка. Оно указывает на то, что данной кнопке панели инструментов поставлена в соответствие команда Лабиринт >- Создать, Имя кнопки. Префикс tlbtn — это сокращение от "ToolButton", "new" означает "новый", a "maze" — "лабиринт".
ShowHint
True
лаби-
Активизация отображения всплывающей подсказки.
6. Аналогичным образом создайте еще две кнопки панели инструментов и измените некоторые их свойства в соответствии с табл. 2.5. Таблица 2.5. Свойства второй и третьей кнопок панели инструментов приложения "Лабиринт" Кнопка (свойство Name)
Свойство
tlbtnOpenMaze
Hint Menultem
• tlbtnEditMaze
ShowHint Hint Menultem ShowHint
Новое значение Открыть лабиринт mmiOperiMaze True Отредактировать лабиринт
mmiEditMaze 1' г и е
60
Глава 2. Приложение "Лабиринт"
Теперь в приложении "Лабиринт" есть панель инструментов с тремя кнопками. Но обычно на кнопках панелей инструментов изображены пиктофаммы. И не только на кнопках панели инструментов, но и рядом с командами меню. Для добавления изображений командам и кнопкам используется специальный KOMnoHeHT'ImageList.
Компонент ImageList Компонент ImageList представляет собой набор пиктограмм, хранящихся на диске в файлах форматов .bmp и . ico. Размеры пиктограмм, соответствуют кнопкам панели инструментов и командам меню. Доступ к каждой такой пиктограмме можно получить при помощи свойства imagelndex — порядкового номера пиктофаммы в наборе. Нумерация пиктограмм начинается с нуля. 1. Расположите на форме fmMain компонент ImageList с вкладки Win32 палитры компонентов (см. рис. 1.4) и присвойте его свойству Name значение ilMain. 2. Для того чтобы создать набор пиктофамм, дважды щелкните мышью на компоненте ilMain. В результате на экране появится редактор компонента ImageList (рис. 2.9). / fmMain.ilMain Image List Setected Image Options
f? о .-
Г
J
OK Cancel
Г -;- •
Рис. 2.9. Редактор компонента ImageList 3. Щелкните на кнопке Add и укажите размещение требуемых графических файлов, чтобы добавить пиктофаммы в набор. Вместе с Delphi поставляется большой набор пиктофамм, которые можно использовать для кнопок и команд меню. При установке Delphi 7 этот набор по умолчанию копируется в каталог \Program Files\Common Files\Borland Shared\Images\Buttons. 4. Добавьте в набор ilMain пиктофамму filenew.bmp. В момент добавления на экране появится запрос о разбиении одной пиктофаммы на две. Ответьте утвердительно, щелкнув на кнопке Yes (Да). В результате в набор вместо одной будет добавлено две пиктограммы. Второй пиктофамме будет соответствовать изображение для неактивной кнопки или команды меню. Если на запрос ответить отрицательно, щелкнув мышью на кнопке No (Нет), то получится одна пиктофамма, созданная из двух изображений (для активного и неактивного состояний кнопки), сжатых по горизонтали.
Создание нового приложения
61
ПРИМЕЧАНИЕ Пиктограммы, соответствующие неактивным кнопкам или командам меню, можно всегда удалить из набора при помощи кнопки Delete. 5. Добавьте последовательно fldropen.bmp и edit.bmp.
в
набор
ilMain
еще
две
пиктограммы:
Если предположить, что были удалены пиктограммы, соответствующие неактивным состояниям кнопок, То окно редактора компонента ilMain, будет выглядеть, как показано на рис. 2.10. 7" ErnMairi.ilMam Imagclist ;;" Selected I mage' Options
Iranspsenl Color.
' ,{ • -Images
Delete
gear
£a»rt... J
Рис. 2.10. Три пиктограммы в наборе ilMain 6. Щелкните на кнопке ОК, чтобы закрыть редактор компонента ilMain. 7. На этом этапе необходимо связать набор пиктограмм ilMain с основным меню и панелью инструментов. Для этого укажите в инспекторе объектов для свойств mmMain. Images и tlbMain. Images ссылку на компонент ilMain (ее можно выбрать из раскрывающегося списка). 8. Теперь можно назначить пиктограммы командам меню и кнопкам панели инструментов. Для этого измените значения свойства imageindex соответствующих объектов, руководствуясь табл. 2.6. В данном случае предполагается, что пиктограммы добавлялись в набор в том порядке, который был представлен выше, а также что были удалены пиктограммы, соответствующие неактивным кнопкам и командам меню. Таблица 2.6. Значения свойства imageindex для кнопок панели инструментов приложения "Лабиринт" и команд одноименного меню Объект (СВОЙСТВО Каше) mmiNewMaze mmiOpenMaze mmiEditMaze tlbtnNewMaze tlbtnOpenMaze tlbtnEditMaze
Значение свойства Imageindex 0
i 2 0 1 2
62
Глава 2. Приложение "Лабиринт"
ПРИМЕЧАНИЕ Доступ к свойствам команд меню можно получить только через редактор меню, который открывается двойным щелчком мышью на компоненте mmMain. В редакторе меню пиктограммы не отображаются в строках меню, даже если указано значение свойства imageindex. Поэтому, если необходимо просмотреть результат назначения пиктограммы элементу интерфейса, перейдите в конструктор форм и откройте соответствующее меню. 9. Сохраните проект и запустите приложение. Как видите, теперь рядом с командами меню Лабиринт и на кнопках панели инструментов отображены соответствующие им пиктограммы (рис. 2.11). 10. Опробовав кнопки панели инструментов (которые еще ничего не выполняют) закройте приложение и возвратитесь в конструктор форм.
Рис. 2.11. Пиктограммы рядом с командами меню и на кнопках панели инструментов
Разработка игрового поля Для организации игрового поля воспользуемся компонентом StringGrid. Он расположен в палитре компонентов (см. рис. 1.4) на вкладке Additional и представляет собой подобие таблицы, в ячейки которой можно заносить различную текстовую или графическую информацию. 1. Расположите на форме fmMain компонент StringGrid и присвойте его свойствам значения в соответствии с табл. 2.7. Таблица 2.7. Свойства компонента StringGrid Свойство
Новое значение
Пояснение
Align
alClient
Color
clWhite
Выравнивание. Значению aiciient соответствует заполнению всего свободного пространства формы. Вместо системного цвета ciwindow лучше использовать стандартный белый цвет.
DefaultColWidth
22
FixedCols
0
FixedRows
0
•
\
Ширина столбцов. Количество фиксированных столбцов. Несколько первых столбцов могут быть зафиксированы. Это означает, что при пролистывании сетки влево или вправо они остаются неподвижными. Количество фиксированных строк. Несколько первых строк могут быть зафиксированы. Это означает, что при пролистывании сетки вверх или вниз они остаются неподвижными.
Создание нового приложения
63
Окончание таблицы 2.7 Свойство
Новое значение
Пояснение
Default Drawing
False
Если это свойство имеет значение True, тогда прорисовка содержимого ячеек выполняется автоматически. Такой вариант используется при работе с текстовой информацией. Если необходимо отображать в ячейках какую-нибудь нестандартную информацию (например, графику), тогда свойству DefaultDrawing необходимо присвоить значение False. Это означает, что разработчик берет на себя решение задачи прорисовки содержимого ячеек. Эта операция выполняется в обработчике события TStringGrid.OnDrawCell. »
-.
Рассматривается в разделе "Рисование структуры лабиринта" этой главы.
DefaultRowHeight
22
Высота строк.
Name
sgMaze
Префикс sg — это сокращение от "StringGrid", a "maze" в переводе с английского означает "лабиринт".
Visible
False
Игровое поле при запуске приложения, должно быть невидимо, поэтому свойству visible присваивается значение False. (Значения, указанные в инспекторе объектов, используются при инициализации компонентов.)
Теперь необходимо создать еще один компонент игрового поля — игрока или "фишку", которая будет передвигаться по лабиринту в поисках выхода. Для этой цели можно применить какой угодно визуальный элемент управления, однако в данном случае проще будет воспользоваться компонентом Panel, расположенным в палитре компонентов на вкладке Standard. 2. Расположите на форме компонент Panel и присвойте его свойствам значения в соответствии с табл. 2.8. 3. Сохраните проект и запустите приложение. Игровое поле и фишка должны быть невидимы, хотя фактически присутствуют на форме. Закройте приложение и вернитесь в конструктор форм.
64
Глава 2. Приложение "Лабиринт"
Таблица 2.8. Свойства компонента Panel Свойство
Новое значение
Пояснение
BevelWidth
2
Создаем широкую фаску, чтобы фишка выглядела на игровом поле более выпуклой.
Caption
Заголовок панели необходимо удалить. Высота "фишки".
Height
16
Name
paPlayer
Префикс ра — это сокращение от "Panel", a "player" в переводе с английского означает "игрок".
Visible
False
Width
16
фишка, так же как и игровое поле, при запуске приложения не видима. Ширина равна высоте.
Разработка редактора лабиринтов Динамическое создание формы Прежде, чем приступить к разработке интерфейса редактора лабиринта, сначала организуем динамическое создание формы fmNewMaze. Редактор лабиринтов должен открываться при помощи команд Лабиринт >• Создать и Лабиринт >Отредактировать. Вначале создадим обработчик события Onclick для команды rnmiNewMaze. Для этого выберите в конструкторе формы команду Лабиринт >• Создать и вставьте в созданный метод mmiNewMazeClick следующий программный код: Application.CreateForra(TfmNewMaze,fmNewMaze); fmNewMaze.Caption := 'Лабиринт - [ Н о в ы й . m a z ] ' ; fmNewMaze.ShowModal; fmNewMaze.Free;
После того, как форма создана, можно изменять значения ее свойств даже до ее отображения на экране. В данном случае инициализируется строка заголовка формы fmNewMaze. В квадратных скобках указывается имя нового лабиринта (файл с расширением .maz), открытого в редакторе в текущий момент. Сохраните проект и запустите приложение. Выполните команду Лабиринт >Создать или щелкните мышью на кнопке панели инструментов приложения "Лабиринт", соответствующей этой команде. В результате будет динамически создана и отображена пустая форма fmNewMaze. Закройте эту форму, а затем выйдите из приложения, и вернитесь в конструктор форм.
Разработка интерфейса редактора лабиринтов В этом разделе вначале будет представлен конечный результат, а затем предложен метод его реализации.
Создание нового приложения
65
Окончательный вид окна редактора лабиринтов представлен на рис. 2.12. Как видно из рисунка, окно редактора лабиринтов разделено на две части. Вверху расположена панель элементов управления — компонент Panel с вкладки Standard палитры компонентов. В нижней части расположена область определения структуры лабиринта — компонент stringGrid, аналогичный тому, который использовался для создания игрового поля на форме fmMain. « См. в разделе "Разработка игрового поля" этой главы. |7''Лабириит - [Ноеый.ша^]
Рис. 2.12. Редактор лабиринтов Из палитры компонентов Delphi на панели элементов управления необходи: мо разместить следующие компоненты: • пять компонентов Label с вкладки Standard — метки; • два компонента SpinEdit с вкладки Samples — счетчики — поля ввода с кнопками инкремента/декремента; • три компонента ColorBox с вкладки Additional — списки выбора цвета; • четыре компонента SpeedButton с вкладки Additional — кнопки выбора режимов; • два компонента BitBtn с той же вкладки Additional — кнопки с пиктограммами. Теперь перейдем непосредственно к разработке интерфейса формы fmNewMaze. 1. Откройте эту форму в конструкторе форм и расположите на ней компонент Panel с вкладки Standard палитры компонентов. Присвойте его свойствам значения в соответствии с табл. 2.9.
66
Глава 2. Приложение "Лабиринт"
Таблица 2.9. Свойства компонента Panel формы fmNewMaze Свойство
Новое значение
Пояснение
Align
alTop
Панель размещается вдоль верхнего края формы. Заголовок необходимо удалить.
Caption Height
112
Высота панели.
Name
paNewMaze
Префикс ра — это сокращение от "Panel", "new означает "новый", a "maze" — "лабиринт".
2. Вернитесь к форме fmMain, выделите компонент игрового поля sgMaze, а затем выполните команду Edit >- Copy или нажмите комбинацию клавиш . В результате компонент sgMaze будет скопирован в буфер обмена. 3. Перейдите к форме fmNewMaze, щелкните мышью на пространстве вне панели paNewMaze, а затем выполните команду Edit >- Paste или нажмите комбинацию клавиш . В результате компонент sgMaze будет вставлен из буфера обмена в форму fmNewMaze и заполнит все пространство, не занятое панелью paNewMaze. ПРИМЕЧАНИЕ Если бы в момент вставки из буфера на форме был выбран компонент Panel, то компонент StringGrid был бы размещен только на этой панели. Такое правило размещения объектов применимо ко всем "панелеобразным" компонентам: Frames, GroupBox, Panel, ScrollBox, ControlBar, TabControl, PageControl, ToolBar, CoolBar. Объекты, размещенные на одном из этих компонентов, становятся по отношению к нему "дочерними" и перемещаются вместе с ним. 4. Присвойте свойству Name только что размещенного на форме fmNewMaze компонента StringGrid значение sgMazeStr, а свойству Visible — значение True. Кроме того, присвойте свойствам ColCount и RowCount значение 10, а из составного свойства Options исключите значение goRangeSelect (измените соответствующее True на False). 5. Расположите на панели paNewMaze компоненты, представленные в табл. 2.10, и измените значения их свойств, приведенные в столбце "Значения" (именам компонентов соответствуют значения свойства Name). Таблица 2.10. Компоненты, размещенные на панели paNewMaze, и значения их свойств Свойство
Пояснение
Значение
Компоненты Label с вкладки Standard. Используются как текстовые, метки или надписи. 1. Компонент laSizeH Caption
Размер поля горизонтали
по
Заголовок, см. на рис. 2.12.
Создание нового приложения
67
Продолжение таблицы 2.10 Свойство
Значение
Пояснение
Left
10
Расстояние в пикселях между левым краем компонента Label и левым краем "родительского" компонента (в данном Случае — paNewMaze).
Тор
20
Расстояние в пикселях между верхним краем компонента Label и верхним краем "родительского" компонента.
\ 2. Компонент laSizeV Caption
Размер
поля
по
вертикали Left
22
Top
52
3. Компонент laWallColor Caption Цвет стен Left
266
Top
20
4. Компонент laEnterColor Gaption
Цвет входа
Left
262
Top
44
5. Компонент laExitColor Caption Цвет выхода Left
254
Top
68
Компоненты SpinEdit с вкладки Samples. Используются для ввода размеров игрового поля по горизонтали и вертикали. Компоненты sdSizeH И sdSizeV Left
165
MaxValue
1000
MinValue
10
Top
11
Для sdSizeH.
43
Для sdSizeV.
Запрет на раничение вого поля. Запрет на раничение вого поля.
ввод числа свыше 1000 — огмаксимального размера игроввод числа меньше 10 — огминимального размера игро-
68
Глава 2. Приложение "Лабиринт"
Продолжение таблицы 2.10 Свойство
Значение
Пояснение
Width
52 Ширина элемента управления. Компоненты ColorBox с вкладки Additional. Используются для выбора цвета прорисовка стен лабиринта и точек входа и выхода. Компоненты cobWallColor, cobEnterColor И cobExitColor Left
324
Selected
clBlack
Для cobWallColor. По умолчанию для рисования стен лабиринта установлен черный цвет.
clGreen
Для cobEnterColor. По умолчанию для рисования точки входа в лабиринт установлен зеленый цвет.
clRed
Для cobExitColor. По умолчанию для рисования точек выхода из лабиринта установлен красный цвет.
Style
cbExtendedColors
=
False cbSystemColors
=
Исключаем из списка дополнительные и системные цвета, оставляя только 16 основных цветов.
False
Top
12
ДЛЯ
36
Для cobEnterColor.
60
ДЛЯ cobExitColor.
cobWallColor.
Ширина выбрана таким образом, чтобы при раскрытии списка не были видны названия цветов. Компоненты SpeedButton с вкладки Additional. Используются в качестве переключателей режима: режим рисования стен лабиринта, режим рисования точки входа, режим рисования точек выхода и режим очистки. Компоненты sbWall, sbEnter, sbExit И sbClear
Width
39
Caption
III
Для sbWall.
>
Для sbEnter.
X
Для sbExit.
0
Для sbClear.
Grouplndex
1
Это свойство используется для объединения нескольких кнопок типа TSpeedButton в одну группу переключателей. Среди всех кнопок с одинаковым значением свойства Grouplndex (больше 0) нажата всегда будет только одна.
Создание нового приложения
69
Продолжение таблицы 2.10 Свойство
Значение
Пояснение
Down
True
Для sbwaii. При инициализации формы редактор должен находиться в режиме рисования стен (нажата кнопка sbwaii).
False
ДЛЯ sbEnter.
False
ДЛЯ sbExit.
False
Для sbClear.
Hint
Режим
рисования
Всплывающая подсказка для sbwaii.
рисования
Всплывающая подсказка для sbEnter.
стен Режим
точки входа Режим .'
рисования
Всплывающая подсказка для sbExit.
точек выхода Режим очистки
Всплывающая подсказка для sbClear.
Left
368
ShowHint
True
Включение отображения всплывающей подсказки.
Top
12
Для sbWall.
36
Для sbEnter.
60
Для sbExit.
84
Для sbClear.
Компоненты BitBtn с вкладки Additional. Первый компонент — кнопка Закрыть, второй — кнопка Сохранить. Компоненты bbClose и bbSave [akTop,
Caption
Закрыть
Для bbClose.
Сохранить
Для bbSave.
False
Для bbSave. При создании формы кнопка Сохранить должна быть недоступна.
Enabled
akRight]
Свойство Anchors используется для привязки (закрепления положения) объектов. В данном случае у кнопок должны быть закреплены положения верхней и правой стороны. Это означает, что при изменении размеров формы fmNewMaze, а вместе с ней и размеров панели paNewMaze, положение кнопок относительно верхнего правого угла формы будет оставаться неизменным.
Anchors
70
Глава 2. Приложение "Лабиринт"
Окончание таблицы 2.10 Свойство
Значение
Пояснение
Kind
bkClose
Для bbClose. Свойство Kind указывает на тип операции, выполняемой при нажатии кнопки. Тип bkClose означает, что для кнопки bbClose обработчик события Onclick не требуется, так как при I
bkCustom •
закрыта. Это свойство компонентов BitBtn часто используется при работе с модальными диалоговыми окнами. Для bbsave. Значение bkCustom указывает на то, что с кнопкой не связано никакой автоматической операции, и для нее можно определить обработчик события OnClick.
Left
430
Top
8
Для bbClose.
40
flnflbbSave.
Width
100
6. Сохраните проект и запустите приложение. 7. Выполните команду Лабиринт >- Создать или щелкните мышью на соответствующей этой команде кнопке панели инструментов. 8. В редакторе лабиринтов исследуйте работу элементов управления, а затем щелкните мышью на кнопке Закрыть, чтобы выйти из приложения "Лабиринт".
Программный код модуля редактора лабиринтов Теперь, когда создана, интерфейсная оболочка приложения, приступим к написанию программного кода.
Рисование структуры лабиринта Весь процесс рисования структуры лабиринта организован при помощи методов обработки двух событий компонента sgMazeStr: OnSelectSell и OnDrawCell. Событие OnSelectCell возникает в момент выбора какой-либо ячейки компонента StringGrid при щелчке мышью на ячейке или нажатии клавиши управления курсором на клавиатуре. В приложении "Лабиринт" используется только рисование при помощи мыши, поэтому для компонента sgMazeStr необходимо отключить обработку нажатий клавиш клавиатуры. Для этого выполните следующие действия. 1. Выделите компонент sgMazeStr.
Создание нового приложения
71
2. Прейдите в инспекторе объектов на вкладку Events и дважды щелкните мышью в поле события OnKeyDown. 3. В сгенерированном методе sgMazeStrKeyDown введите между ключевыми словами begin и end следующий оператор:
Событие OnKeyDown возникает в момент нажатия клавиши на клавиатуре и обрабатывается перед передачей соответствующего кода клавиши элементу управления. Коду нажатой клавиши соответствует параметр Key. Таким образом, в представленном выше операторе этот код принудительно "обнуляется", в результате чего элемент управления не обрабатывает событие нажатия клавиши. ПРИМЕЧАНИЕ Существует еще одно событие нажатия клавиши — OnKeyPress. Оно отличается от события OnKeyDown тем, что параметр Key, передаваемый в метод обработки, имеет не целочисленный, а символьный тип Char. Таким образом, в приложении "Лабиринт" вместо обработчика события OnKeyDown можно было бы создать обработчик события OnKeyPress и добавить в него для "обнуления" нажатия клавиши оператор Key := C h r ( 0 ) ; или Key := #0; НЕМНОГО ТЕОРИИ Каждому символу, как видимому, так и невидимому, а значит и каждой клавише клавиатуры соответствует определенный код ASCII. ASCII — это аббревиатура от полного названия кодировки символов "American Standard Code for Information Interchange" (Американский стандарт кодировки для обмена информацией). Например, клавише <Escape> соответствует код 27, клавише <Enter> — код 13, а английской букве "А" — код 65. Для получения символа по его коду в языке Object Pascal используется либо функция Chr, либо запись вида #код. Например, чтобы присвоить некоторой строковой переменной строку МАМА, состоящую из английских букв, можно воспользоваться одним из представленных ниже операторов: s
:=
'МАМА';
s := C h r ( 7 7 ) + C h r ( 6 5 ) + C h r ( 7 7 ) + C h r ( 6 5 s := #77 + #65 + #77 + #65; s := tt77#65#77#65;
4. Выделите на форме компонент sgMazeStr, перейдите в инспектор объектов на вкладку Events и дважды щелкните мышью в поле события OnDrawCell (как уже упоминалось выше, это событие возникает при щелчке мышью на одной из ячеек компонента stringGrid). В результате будет создан метод обработки этого события - - sgMazeStrSelectCell. Вставьте в него программный код, который в зависимости от текущего режима, определяемого набором кнопок sbWall, sbExit, sbEnter и sbClear, будет заносить соответствующий символ в выбранную ячейку компонента sgMazeStr:
72
Глава 2, Приложение "Лабиринт"
procedure TfmNewMaze.sgMazeStrSelectCell(Sender: TObject; ACol, ARow: Integer; var CanSelect: Boolean); begin if sbWall.Down then sgMazeStr.Cells[ACol, ARow] := '['; if sbEnter.Down then sgMazeStr.Cells[ACol, ARow] := '>'; if sbExit.Down then sgMazeStr.Cells[ACol, ARow] := 'X'; if sbClear.Down then sgMazeStr.Cells[ACol, ARow] := '0'; end,-
Выбранная в данный момент ячейка определяется по значению параметров ACol (номер столбца) и ARow (номер строки). Так, первый оператор метода sgMazeStrSelectCell можно прочитать следующим образом: если нажата кнопка sbWall, то в текущую ячейку компонента sgMazeStr, расположенную на пересечении столбца с номером ACol и строки с номером ARow, заносится символ "|". В режиме рисования точки входа (нажата кнопка sbEnter) в выбранную ячейку структуры лабиринта заносится символ ">", в режиме рисования точек выхода (нажата кнопка sbExit) — символ "X", а в режиме очистки (нажата кнопка sbClear) — символ "О". 5. Запустите приложение "Лабиринт", откройте окно редактора лабиринтов и пощелкайте мышью в ячейках компонента sgMazeStr в разных режимах (при различных нажатых кнопках). Ничего не происходит? Верно! Ничего и не должно произойти. Почему? Потому что прежде свойству sgMazeStr. DefaultDrawing было присвоено значение False, и теперь за прорисовку содержимого ячеек отвечает разработчик. Другими словами, символы в ячейки заносятся, что можно проверить, присвоив свойству sgMazeStr .DefaultDrawing значение True, но не отображаются. « См. подробнее о свойствах sgMazeStr.DefaultDrawing'в разделе "Разработка игрового поля" этой главы.
6. Закройте редактор лабиринтов и выйдите из приложения. 7. Прорисовка содержимого ячеек компонента StringGrid выполняется в обработчике события OnDrawCell.' Выполнив последовательность действий, описанную в пункте 4, создайте при помощи инспектора объектов обработчик события sgMazeStr .OnDrawCell и вставьте в него следующий программный код: with sgMazeStr do begin if Cells[ACol, ARow] = '!' then begin . Canvas.Brush.Color := cobWallColor.Selected; Canvas.FillRect(Rect); end; if Cells[ACol, ARow] = 'X' then
Создание нового приложения
73
begin Canvas.Brush.Color := cobExitColor.Selected; Canvas.FillRect(Rect); end; if Cells[ACol, ARow] = '>' then begin Canvas.Brush.Color := cobEnterColor.Selected; Canvas.FillRect(Rect); end; if Cells[ACol, ARow] = I 0 I then begin Canvas.Brush.Color := clWhite; Canvas.FillRect(Rect); end; end;
НЕМНОГО ТЕОРИИ Конструкция with...do используется для сокращения записи при обращении к свойствам и методам объектов. Так операторы Panel.Color := clRed; Panel.Left
:= 100;
Panel.Height := 50; Panel.Width := 200; можно записать по-другому: with Panel do begin Color := clRed; Left
:= 100;
Height := 50; Width := 200; . end;
Кроме того, между ключевыми словами with и do можно указывать через запятую несколько объектов, например: with Panel, BitBtn do. В этом случае просмотр объектов при поиске соответствий выполняется слева направо. Так, во фрагменте with Panel, BitBtn do begin Caption := ' H e l l o ' ;
Kind := bkClose; end; свойство Caption будет сопоставлено с объектом Panel, так как объект Panel следует в перечне первым, хотя такое свойство есть и у объекта BitBtn, а свойство Kind будет сопоставлено с объектом BitBtn, так как у объекта Panel такого свойства нет.
74
Глава 2. Приложение "Лабиринт"
НЕМНОГО ТЕОРИИ Подобная запись действительно бывает очень удобной, особенно если объекты имеют длинные имена. Тем не менее, слишком увлекаться такими сокращениями не стоит, так как вложенные структуры with...do ухудшают читабельность программного кода и нередко приводят к путанице и ошибкам. В методе sgMazeStrDrawCell проверяется содержимое ячейки, а затем она закрашивается цветом, соответствующим вставленному в нее символу. Так, во фрагменте if Cells[ACol, ARow] = ' [ ' then begin Canvas.Brush.Color := cobWallColor.Selected; Canvas.FillRect(Rect); end;
происходят следующие действия. Если в ячейку, лежащую на пересечении столбца с номером ACol и строки с номером ARow, вставлен символ "|" (соответствует стене лабиринта), тогда устанавливается цвет заливки, выбранный в списке cobWallColor. После этого при помощи метода Canvas .FillRect цветом заливки закрашивается прямоугольная область, соответствующая данной ячейке (определена параметром Rect типа TRect). Аналогичные проверки выполняются и для остальных трех возможных элементов лабиринта, причем символу "О" (соответствует проходу лабиринта) поставлен в соответствие белый цвет. В методе sgMazeStrDrawCell используется свойство Canvas. Этим свойством обладают многие визуальные компоненты, и по существу оно само является объектом, для которого определен большой набор свойств и методов рисования. Например, свойство Canvas. Brush определяет характер заливки, свойство Canvas. Pen — тип линий при рисовании, метод Canvas. FillRect заполняет прямоугольную область текущим цветом заливки, а метод Canvas.Rectangle рисует прямоугольник. 8. Сохраните проект, запустите приложение, откройте редактор лабиринтов и пощелкайте мышью на компоненте sgMazeStr в различных режимах рисования. Теперь ячейки должны заполняться цветом, соответствующим текущему режиму рисования. Обратите внимание на то, что можно закрашивать несколько ячеек подряд, если перемещать указатель мыши, удерживая нажатой левую кнопку. 9. Выйдите из редактора лабиринтов и закройте приложение. Теперь создадим обработчики событий для тех элементов управления, которые определяют цвета, используемые для рисования лабиринта. 10. Для компонентов cobWallColor, cobEnterColor и cobExitColor создайте при помощи инспектора объектов обработчики события OnChange, и вставьте в них представленный ниже программный код: "
Создание нового приложения
75
procedure TfmHewMaze.cobWallColorChange(Sender:' TObject); begin if (cobWallColor.Selected = cofoExitColor.Selected) or (CobWallColor.Selected = cobEnterColor.Selected) or (cobWallColor.Selected = clWhite) then MessageDlg('Этот цвет уже используется для друзтих элементов!', mtWarning, [шЬОК] ,0) ; sgMazeStr.Invalidate; end; procedure TfmMewMaze.cobExitColorChange(Sender г TObject); begin if (cobExitColor.Selected = cobWallColor.Selected) or (cobExitColor.Selected = cobEnterColor.Selected) or (cobExitColor.Selected = clWhite} 1 then MessageDlg('Этот цвет уже используется для других элементов' , mtWarning,[mbOK] ,0) ; sgMazeStr.Invalidate; end; procedure TfmNewMaze.cobEnterColorChange(Sender: TObject); begin if (aobEnterColor.Selected = cobExitColor.Selected) or (cobEnterColor.Selected = cobWallColor.Selected) or (cobWallColor.Selected = clWhite) then MassageDlgf'Этот цвет уже используется для других элементов!', mtWarning,[mbOK],0); sgMazeStr.Invalidate; end; Событие OnChange возникает тогда, когда пользователь выбирает в списке какой-нибудь новый элемент. В каждом из представленных выше методов вначале выполняется сопоставление выбранного цвета (свойство Selected) с текущими цветовыми значениями, установленными для рисования других элементов лабиринта. Если выбранный цвет уже используется для других элементов, то на экран при помощи процедуры MessageDlg выводится соответствующее сообщение. В конце обработчика события OnChange вызывается метод sgMazeStr.Invalidate, который обновляет компонент sgMazeStr. В результате возникает событие sqMazeStr .OnDrawCeil, обработчик которого перерисовывает структуру лабиринта в соответствии с новыми цветовыми установками. 11. Сохраните проект, запустите приложение, откройте редактор лабиринтов, и нарисуйте при помощи мыши несколько элементов лабиринта.
76
Глава 2. Приложение "Лабиринт"
12. Затем выберите в раскрывающихся списках другие цвета и убедитесь, что цвет нарисованных элементах изменится. 13. Закройте редактор лабиринтов и выйдите из приложения. Теперь создадим обработчики событий для компонентов sdSizeH и sdSizeV, которые используются для установки размеров игрового поля. 14. Создайте для этих элементов управления обработчики события OnChange, и вставьте в них представленный ниже программный код: procedure TfmNewMaze.sdSizeHChange(Sender: TObject]; begin sgMazeStr.ColCount := sdSizeH.Value; end;
procedure TfmNewMaze.sdSizeVChange(Sender: TObject); begin sgMazeStr.RowCount := sdSizeV.Value; end;
Как видите, эти методы очень просты. В них изменяется количество столбцов или строк компонента sgMasterStr в соответствии со значениями, указанными в компонентах sdSizeH и sdSizeV. 15. Запустите приложение, проверьте работу этих методов на практике, а затем вернитесь в редактор исходного кода. Вот, собственно, и все, что касается рисования структуры лабиринта. Теперь в модуле редактора лабиринта осталось определить только три метода. Один из них — это процедура проверки корректности лабиринта, а два остальных — это методы обработки событий frnNewHaze.OnClose и bbSave.Onclick.
Процедура проверки корректности структуры лабиринта Структура лабиринта считается корректной, если в нем есть только одна точка входа и хотя бы одна точка выхода. Если какое-либо из этих условий не выполняется, лабиринт не может быть сохранен, то есть кнопка bbSave будет недоступна. Кроме того, если уже создана точка входа, соответствующая ей кнопка режима рисования sbEnter также должна стать недоступной. 1. Перейдите в редактор исходного кода и найдите в интерфейсном разделе модуля MazeNew.pas ключевое слово private. После него определяются так называемые "закрытые" члены класса TfmNewMaze. Именно к таким членам и будет относиться процедура проверки корректности лабиринта. НЕМНОГО ТЕОРИИ Все члены класса (свойства и методы) по характеру доступа к ним делятся на четыре категории: закрытые (private), защищенные (protected), открытые (public)'и опубликованные (published). Соответствующим образом, в программных модулях языка Object Pascal для определения членов классов используются разделы private, protected, public и published.
Создание нового приложения
77
НЕМНОГО ТЕОРИИ Элементы класса, определенные в разделе public, без каких-либо ограничений открыты для доступа извне. Без ограничений доступны также и элементы, определенные в разделе published, однако они отличаются тем, что для них генерируется информация о типах времени выполнения (RTTI — Run Time Type Information). Информация RTTI используется для различных целей: например, для организации отображения значений свойств в инспекторе объектов. Элементы, определенные в разделе private, недоступны за пределами программного модуля, в котором объявлен класс. Другими словами, закрытые члены класса, нельзя вызвать из другого модупя. Элементы, определенные в разделе protected, доступны в модуле, в котором объявлен класс, а также доступны всем членам классов, производных от данного класса. По умолчанию при создании программных модулей Object Pascal раздел protected не создается, а раздел published не указывается явно. Все члены класса, которые не определены в разделах private и public, no умолчанию относятся к категории published.
2. Вставьте в раздел private следующее объявление процедуры: private
{ Private declarations ( procedure CheckMaze;
3. Теперь опуститесь чуть ниже, в раздел реализации implementation), и создайте тело процедуры CheckMaze:'
(после
procedure ТfmNewMaze.CheckMaze; var i, j, cEnter, cExit: integer; begin cEnter : = 0 ; cExit := 0,for i := 1 to sdSizeH.Value do for j := 1 to sdSizeV.Value do begin //подсчет количества входов if sgMazeStr.Cells[i-l,j-l]= '>' then inc(cEnter]; ,, //подсчет количества выходов if sgMazeStr.Cells[i-1,j-l]='X' then inc(cExit); end; bbSave.Enabled ;= (cEnter - 1) and (cExit > 0); // Разрешается создавать только одну точку входа sbEnter.Enabled := cEnter = 0; end;
слова
78
Глава 2. Приложение "Лабиринт"
ПРИМЕЧАНИЕ Обратите внимание на символы комментариев. В языке Object Pascal применяется два вида комментариев: однострочные (начинаются с символов "//"} и многострочные, когда фрагмент текста заключен в фигурные скобки {...}. Давайте рассмотрим, что же происходит в этой процедуре. Вначале, после ключевого слова var f объявляются четыре целочисленных переменных типа integer. Две из них, i и j, будут использоваться в качестве счетчика цикла, а остальные две, cEnter и cExit, — для подсчета количества точек входа и точек выхода соответственно. Далее при помощи двух вложенных циклов for организован последовательный просмотр содержимого ячеек компонента sgMazeStr. Обратите внимание на то, что начальным значением счетчиков цикла является 1, а при обращении к ячейкам sgMazeStr при помощи свойства Cells используются значения счетчиков, уменьшенные на единицу. Это объясняется тем, что нумерация строк и столбцов компонента StringGrid начинается с нуля, а не с единицы. Кнопка bbSave является доступной (свойству Enabled присваивается значение True) только в том случае, если в структуре лабиринта присутствует только одна точка входа (cEnter = 1) и хотя бы одна точка выхода (cExit > 0). По правилам игры разрешается создавать только одну точку входа. Поэтому кнопка перехода в соответствующий режим рисования (sbEnter) может быть доступна только в том случае, если в структуре лабиринта не определено ни.одной точки входа (cEnter = 0). 4. Теперь необходимо вставить вызовы процедуры CheckMaze в методы обработки событий sgMaze.OnSelectCell, sdSizeH.OnChange И sdSizeV.OnChangel procedure TfmNewMaze.sgMazeStrSelectCell(Sender: TObject; ACol, ARow: Integer,- var CanSelect: Boolean); begin if sbWall.Down then sgMazeStr.Cells[ACol, ARow] := 'Г; if sbEnter.Down then sgMazeStr.Cells[ACol, ARow] := '>'; if sbExit.Down then sgMazeStr.Cells[ACol, ARow] := 'X'; if sbClear.Down then sgMazeStr.Cells[ACol, ARow] := '0'; CheckMaze; end;
procedure TfmNewMaze.sdSizeHChange(Sender: TObject); begin sgMazeStr.ColCount := sdSizeH.Value; CheckMaze; end;
Создание нового приложения procedure TfmNewMaze.sdSizeVChange(Sender: begin
5. Сохраните проект, запустите приложение и нарисуйте в редакторе различные элементы лабиринта, наблюдая за поведением кнопок Сохранить и Режим рисования точки входа. 6. Закройте приложение и вернитесь в конструктор формы frnNewMaze. Теперь осталось только определить методы обработки событий fmNewMaze.OnClose И bbSave.OnClick.
Обработчик события fmNewMaze.OnClose В начале создадим обработчик события fmNewMaze.OnClose, который вызывается в момент закрытия формы fmNewMaze. Для определения операций, выполняемых при закрытии формы, нельзя использовать обработчик события bbciose.Onclick, так как свойство bbClose.Kind имеет значение bkciose, a обработчик события OnClick вызывается только в том случае, если свойство Kind имеет значение bkCustom. 1. Выделите объект fmNewMaze, выбрав его в раскрывающемся списке, расположенном в верхней части инспектора объектов, или щелкнув мышью на корневом элементе в окне Object TreeView. 2. Перейдите в инспекторе объектов на вкладку Events, создайте обработчик события fmNewMaze.OnClose и вставьте в него следующий программный код: if bbSave,Enabled then if MessageDlg('Сохранить лабиринт?', mtConfirmation,[mbYes,mbNo],0) = mrYes then begin bbSave.Click; Close; end; В этом методе вначале выполняется проверка доступности кнопки bbSave. Если эта кнопка доступна, то в структуру лабиринта были внесены изменения. Если в ответ на запрос пользователь нажмет кнопку Yes (Да), то при помощи метода Click имитируется щелчок мышью на кнопке bbSave (в результате чего будет вызван обработчик события bbSave.OnClick), а затем форма будет закрыта при помощи метода close.
80
Глава 2. Приложение "Лабиринт"
Обработчик события bbSave.OnCIJck Прежде, чем создать обработчик события bbSave.OnClick необходимо разместить на форме fmNewMaze компонент диалогового окна сохранения файлов. Компоненты диалоговых окон находятся в палитре компонентов на вкладке Dialogs. Диалоговому окну сохранения файлов соответствует компонент SaveDialog. 1. Расположите компонент SaveDialog на форме fmMewMaze. 2. Присвойте свойству Name значение sd_i, а свойству Filter — значение Лабиринт|*.maz. Для свойства Filter существует редактор, который вызывается двойным щелчком мыши на соответствующем поле в инспекторе объектов (рис. 2,13). В этом редакторе можно определить фильтрацию типов сохраняемых файлов. В приложении "Лабиринт" используется только один тип файлов — .maz, и поэтому требуется создать только один фильтр под названием Лабиринт.
Рис. 2.13. Редактор свойства Filter
ПРИМЕЧАНИЕ Для того чтобы протестировать компонент диалогового окна, не запуская приложение, можно дважды щелкнуть на нем мышью в конструкторе форм. 3. Создайте обработчик события bbSave.OnClick. Для этого можно воспользоваться либо инспектором объектов, либо просто дважды щелкнуть мышью на кнопке bbSave в конструкторе форм. ПРИМЕЧАНИЕ Событие OnClick для кнопок является событием по умолчанию, то есть таким событием, для которого обработчик можно создавать при помощи двойного щелчка мыши в конструкторе форм. События по умолчанию определены для всех визуальных компонентов Delphi. Например, для раскрывающихся списков и текстовых полей событием по умолчанию является OnChange. 4. Внесите изменения в метод bbsaveClick в соответствии с представленным ниже фрагментом: procedure IfmNewMaze.bbSaveClickfSender: TObject); var okSave: boolean; i, j: integer; F: TextFile; begin sd 1 .InitialDir := Extr/aatFilePath (Application.ExeName) ;
Создание нового приложения
81
sd__l . FileName := copy (Caption, pos { ' [ ' , Caption) -f 1 , pos ( ' ] ' , Caption) -pos (' t ' , Caption) -1) ; okSave := False; while not okSave do if sd_l. Execute then begin if FileExists (sd_l .FileName) 1 then okSave := (MessageDlg( ' Такой файл уже существует' + t!3 + 'Сохранить поверх его?', mtConfirmation, [mbYes ,mbNo] ,0) = mryes) else okSave := True,1
Рассмотрим этот метод по частям. sd_l.InitialDir := ExtractFilePath(Application.ExeMame); Свойство Application.ExeName содержит полный путь и имя файла выполняемого приложения. Функция ExtractFilePath извлекает из этого имени путь и использует его для инициализации свойства sd_l. initialDir. Свойству initialDir соответствует каталог, который является текущим при активизации компонента SaveDialog. sd_l.FileName := copy(Caption, pos('[',Caption)+1, pos(']'.Caption)-pos('[',Caption)-!);
82
Глава 2. Приложение "Лабиринт"
Б правой части этого оператора при помощи функций сору и роз извлекается часть заголовка формы, расположенная между квадратными скобками, то есть, название текущего открытого в редакторе лабиринта. Затем полученное имя лабиринта используется в качестве текущего имени файла при открытии диалогового окна sd_l. okSave := False; while not okSave do Здесь организован цикл с предусловием, который выполняется до тех пор, пока булева переменная okSave имеет значение False. if sd__l .Execute then begin end else break; Метод Execute используется для активизации диалоговых окон. Если в диалоговом окне была нажата кнопка подтверждения (в данном случае -- кнопка Save или Сохранить), тогда метод Execute возвращает значение True, в противном случае происходит принудительный выход из внешнего цикла при помощи процедуры break. if FileExists(sd_l.FileHame) then okSave := (MessageDlg('Такой файл уже существует!' + #13 + 'Сохранить поверх его?', mtConfirraation, [mbYes,mbNo], 0) = mrYes) else okSave :- True; Если файл, выбранный в диалоговом окне сохранения файла, уже существует, то переменной okSave будет присвоено значение True в том случае, если пользователь подтвердит сохранение файла поверх старого. Функция MessageDlg возвращает результат в соответствии с нажатой кнопкой. Символ #13 — это символ перевода на новую строку. Обратите также внимание на то, что при работе со строковыми значениями типа string можно использовать операцию сложения или конкатенации +. Если файл, указанный в диалоговом окне не существует, тогда переменной okSave присваивается значение True — выход из никла. if pos('.maz', sd_l.FileName) = 0 then AssignFile(F,sd_l-FileName + '.maz') else AssignFile(F,sd 1.FileName); В этом фрагменте проверяется наличие в имени файла расширения .maz, указанного в диалоговом окне. Затем при помощи процедуры AssignFile файл с выбранным именем ставится в соответствие файловой переменной F. Подобная методика используется только при работе с текстовыми (как в данном примере) и типизированными файлами. Организация доступа к двоичным файлам через файловые потоки в данной книге не рассматривается.
Создание нового приложения
83
Rewrite(F); WriteLn(F,cobWallColor,Selected) ; WriteLn(F,cobEnterColor.Selected); HriteLn(F,cobExitColor.Selected); В этом фрагменте текстовый файл F открывается для записи, и в него последовательно записываются значения цветов, выбранных для рисования элементов лабиринта. В случае применения процедуры Rewrite к уже существующим файлам, их старое содержимое будет потеряно. for i := 1 to sdSizeV.Value do begin , for j := 1 to sdSizeH.Value do if sgMazeStr.Cells[j-1,i-1] = '' then Write(F,'0'i else Write(F,sgMazeStr.Cells[j-1,i-1 WriteLn(F,''} end;
В этом фрагменте в цикле просматриваются ячейки компонента sgMazeStr и их содержимое последовательно записывается в файл F. Если ячейка ничего не содержит, то ей соответствует символ "О". Процедура WriteLn отличается от процедуры W r i t e тем, что она в конце записываемых данных добавляет символ конца строки. CloseFile(F); bbSave.Enabled := False; Caption := 'Лабиринт - [' + ExtractFileName(sd l.FileName] + ' ] ' ;
Файл F закрывается, кнопка bbSave становится недоступной, а в заголовке формы fmNewMaze указывается имя файла сохраненного лабиринта. Функция Extr act Filename подобна функции ExtractFilePath с той только разницей, что она используется для извлечения имени и расширения файла. Вот и все, что касается редактора лабиринтов. 5. Сохраните проект, запустите приложение и выполните команду меню Лабиринт >- Создать. 6: В редакторе создайте какой-либо лабиринт, например, такой, как на рис. 2.14. 7. Щелкните мышью на кнопке Сохранить и сохраните созданный "шедевр" в файле с именем Проба.rnaz. 8. Щелкните мышью на кнопке Закрыть, а затем выйдите из приложения.
Программный код основного модуля приложения В этом разделе будет разработан программный код загрузки лабиринта в игровое поле, открытие редактора для уже существующего лабиринта и, конечно же, программный код реализации самой игры.
84
Глава 2. Приложение "Лабиринт" 7 Ллбиринг - [Новый.таг] Рэзмар гкмя|» горимкга™ I Размер гага по ввртжли 1
-2J -И
_ Цвет Еища JTJ.IJ > | Пит выхода |B^j f X
ттттттттт
РИС. 2.14. ЛабирИНТ Проба. maz
Загрузка лабиринта в игровое поле и в редактор лабиринтов 1. Для организации загрузки лабиринта и реализации функциональности игры предварительно объявим в разделе private модуля-MazeMain.pas несколько глобальных переменных, а также процедуру LoadMaze: private 1 { Private declarations } WallColor, EnterColor, ExitColor: TColor; CurCol, CurRow: integer; procedure LoadMaze(sgForLoad: TStringGrid; forGsme: boolean); Переменным WallColor, EnterColor и ExitColor соответствуют цвета элементов лабиринта, которые будут считываться из внешнего файла с расширением .таг. Целочисленным переменным CurCol и CurRow будет соответствовать текущая позиция "фишки" в игровом поле. В процедуре загрузки лабиринта LoadMaze объявлены два параметра: sgForLoad типа TStringGrid и forGame типа boolean. Наличие первого параметра обусловлено тем, что функция LoadMaze будет использоваться не только для загрузки лабиринта в игровое поле, но и для открытия уже существующего лабиринта в редакторе. В первом случае в качестве параметра sgForLoad будет передаваться имя компонента sgMaze (параметр forGame имеет значение True), а во втором — имя компонента sgMazeStr (параметр forGame имеет значение False). 2. Прежде чем приступить к созданию процедуры LoadMaze, расположите на форме fmMain компонент OpenDialog с вкладки Dialogs.
Создание нового приложения
85
3. Присвойте его свойству Name значение od_i, свойству DefaultExt значение * .таг, свойству Filter значение Лабиринт | * .таг, а свойству Title значение Открыть лабиринт. 4. Теперь создайте тело процедуры LoadMaze в разделе реализации модуля MazeMain.pas: procedure TfmMain.LoadMaze(sgForLoad: TStringGrid; forGame: boolean); var F:" TextFile; i: integer,sRow: string; begin od_l.initialDir : = ExtractFilePath(Application.ExeName); if od_l.Execute then begin if forGame then Caption := 'Лабиринт -['•*• ExtractFileName(od_l. FileName) •*-']' else fmHewMaze.Caption := 'Редактирование лабиринта - [' + ExtractFileName(od_l.FileName) + ']'; sgForLoad.RowCount := 1; AssignFile(F,od_l.FileKame); Reset fF); Readln(F.WallColor); Readln(F,EnterColor); Readln(F,ExitColor); while not EOFfF) do begin readln(F,sRow); sgForLoad.ColCount := length(sRow); for i := 1 to length(sRow) do begin sgForLoad.Cells[i-1,sgForLoad.RowCount-1]:=sRow[i]; if forGame then if sRowfi] = '>' then begin paPlayer.Top := sgForLoad.Top + (sgForLoad.RowCount-1)*(sgForLoad.DefaultRowHeight + sgForLoad.GridLineWidth) + 5; paPlayer.Left := sgForLoad.Left + (i-1)*(sgForLoad.DefaultColHidth + SgForLoad.GridLineHidth) + 5; CurCol := i - 1;
86
Глава 2. Приложение "Лабиринт" CurRow ;= sgForLoad.RowCount - 1; end;
end; sgForLoad.RowCount := sgForLoad.RowCount + end; CloseFile(F); sgForLoad.RowCount := sgForLoad.RowCount - 1; if forGarne then begin sgForLoad.Visible := True; sgForLoad.SetFocus; paPlayer.Color := ExitColor; paPlayer.Visible := True; end else begin fmNewMaze.sdSizeH.Value ;= sgForLoad.ColCount; fmNewMaze.sdSizeV.Value := sgForLoad.RowCount; fmWewMa2e.cobWallColot.Selected := WallColor; fmMewMaze.cobExitColor.Selected ;= ExitColor; fmNewMase.cobEnterColor.Selected := EnterColor; end; end; end;
Рассмотрим работу только ключевых фрагментов этой процедуры. if
forGame
then Caption := 'Лабиринт - [' + EKtractFileName(od_l.FileName] + ']' else fraNewMaze.Caption := 'Редактирование лабиринта - [' + ExtractFileHame(od_l.FileMame] + ']'; После определения рабочего каталога и выбора файла лабиринта, который необходимо открыть, выполняется проверка назначения этого лабиринта. Если параметр forGarae имеет значение True, тогда лабиринт загружается в игровое поле, а его название отображается в заголовке формы fmMain. В противном случае лабиринт загружается в редактор лабиринтов, а его название отображается в заголовке формы fmNewMaze. sgForLoad. RowCount : =_ 1;
^^^^
Компонент StringGrid, в котором будет отображен загружаемый лабиринт, переводится в исходное состояние, то есть количество строк уменьшается до одной. Reset(F); Readln(F,WallColor);
Создание нового приложения
87
Readln(F,EnterColor); Readln(F,ExitColor); Процедура Reset открывает текстовый файл F (выбранный файл с расширением .maz) для чтения, после чего из него последовательно считываются и заносятся в переменные WallColor, EnterColor и ExitColor значения цветов для рисования элементов лабиринта. while not EQF(F) do begin readln(F,sRow); end;
Это цикл построчного считывания содержимого ячеек лабиринта до тех пор, пока не будет достигнут конец файла F. Каждая прочитанная строка заносится в переменную sRow, после чего указатель позиции в файле перемещается по направлению к концу файла. После считывания последней строки функция EOF возвратит значение True, и произойдет выход из цикла. sgForLoad.ColCount := length(sRow);
^^
Количество столбцов в игровом поле устанавливается равным количеству символов в строке, считанной из файла. for i := 1 to lengthfsRow) do begin sgForLoad.Cells[i-1,sgForLoad.RowCount-l]:=sRowfi]; end;
В этом цикле выполняется посимвольный просмотр прочитанной строки, содержащей обозначения элементов лабиринта, с занесением этих обозначений в соответствующие ячейки компонента stringGrid. if forGame then if sRow[i] = •>' then . begin paPlayer.Top := sgForLoad.Top + (sgForLoad.RowCount-1)*(sgForLoad.DefaultRowHeight + sgForLoad.GridLineWidth) + 5; paPlayer.Left := sgForLoad.Left + (i-1)*(sgForLoad.DefaultColWidth + sgForLoad.GridLineWidth) + 5; CurCol := i - 1; CurRow ;= sgForLoad.RowCount - 1; end;
88
Глава 2. Приложение "Лабиринт"
Если лабиринт загружается в игровое поле, то необходимо определить позицию точки входа (sRow[i] = '>'). Затем в соответствующей ячейке со смещением в 5 пикселей по горизонтали и вертикали размещается фишка (paPlayer.Left, paPlayer.Top). После этого глобальным переменным CurCol и CurRow, определяющим позицию игрока на игровом поле, присваивается номера текущего столбца и текущей строки компонента stringGrid. sgForLoad.RowCount := sgForLoad.RowCount + 1; Количество строк игрового поля увеличивается на 1 для последующего занесения информации, считанной из файла *.maz. if forGame then begin sgForLoad.Visible := True; sgForLoad.SetFocus; paPlayer .Color. := ExitColor; paPlayer.Visible := True; end else begin fmNewMaze.sdSizeH.Value := sgForLoad.ColCount; fmNewMaze.sdSizeV.Value := sgForLoad.RowCount; fmNewMaze.cobWallColor.Selected := WallColor; frnNewMaze . cobExitColor . Selected : = ExitColor; fmNewMaze.cobEnterColor.Selected :- EnterColor; end;
Если лабиринт загружается для игры, то игровое поле отображается, и в него переносится фокус приложения. Кроме того, "фишке" присваивается цвет точки выхода, после чего она также отображается в стартовой позиции игрового поля. Если лабиринт загружается в редактор лабиринтов, то устанавливаются необходимые свойства элементов управления, размещенных на форме fmNewMaze (см. рис. 2.14). Продолжим разработку программного кода основного модуля приложения "Лабиринт". 5. Создайте обработчики событий rnmiOpenMaze. OnClick и mrniEditMaze.OnClick. 6. В конструкторе формы fmMain выполните команду Лабиринт >~ Открыть и в автоматически созданный метод mmiopenMazeClick введите следующий оператор: LoadMaze(sgHaze, True) ; 7. Повторите ту же операцию для команды Лабиринт >- Отредактировать и в автоматически созданный метод mmiEditMazeClick введите следующие строки программного кода:
Создание нового приложения
89
sgMaze.Visible := False; paPlayer.Visible := False; Caption := 'Лабиринт';. Application.CreateForm(TfmNewMaze,fmNewMaze); LoadMaze (fmNewmaze. sgMazeStr, False) ; fmNewMaze.ShowModal; fmNewMaze .Free; ' В первых трех строках этой процедуры игровое поле и фишка становятся невидимыми, а заголовку формы froMain присваивается просто строка "Лабиринт". В остальной части процедуры создается форма froNewMaze, и в сетку редактора sgMazeStr загружается содержимое выбранного файла * .maz. 8. Теперь необходимо реализовать обработчик события sgMaze.OnDrawCell. В противном случае элементы лабиринта не отобразятся в игровом поле. Создайте этот обработчик события и введите в него следующий программный код: with sgMaze do begin if Cells[ACol, ARow] - ' ' then begin Canvas-Brush.Color := WallColor; Canvas.FillRect(Rect); end;
if Cells[ACol, ARow] = 'X' then begin Canvas.Brush.Color := ExitColor; Canvas.FillRect(Rect); end;
if Cells[ACol, ARow] = '>' then begin Canvas.Brush.Color := EnterColor; Canvas.FillRect(Rect); end;
if Cells[ACol, ARow] = '0' then begin Canvas.Brush.Color := clWhite; Canvas.FillRect(Rect) ; end; end;
Как видно из выше приведенного листинга, эта процедура аналогична процедуре sgMazeStrDrawCell, реализованной в модуле MazeHew.pas. «
См. подробнее о модуле MazeNew.pas в разделе "Программный код модуля редактора лабиринтов" этой главы.
90
Глава 2. Приложение "Лабиринт"
9. Сохраните проект и запустите приложение. 10. Загрузите лабиринт Проба.таг сначала в игровое поле при помощи команды Лабиринт >- Открыть, а затем — в редактор лабиринтов при помощи команды Лабиринт >- Отредактировать. Теперь осталось только разработать программный код реализующий функции самой игры "Лабиринт" — заставить "фишку" двигаться в игровом поле по командам пользователя.
Реализация игровых функций игры "Лабиринт" Вся функциональность игры реализуется всего лишь в одном методе — обработчике события sgMaze.OnKeyDown. Создайте этот обработчик и вставьте в него следующий программный код: with sgMaze do begin case key of vk_Right: if (CurCoK (ColCount-1) ) and (Cells[CurCol+l,CurRow]<>'I') then begin paPlayer.Left := paPlayer.Left + DefaultColWidth + GridLineWidth; inc(CurCol); end; vk_Left: if (CurCol>0) and (Cells[CurCol-l,CurRow]<>'I') then begin paPlayer-Left := paPlayer.Left DefaultColWidth GridLineWidth; dec (CurCol}; end; vk_Down: if (CurRow<(RowCount-1)I and (Cells[CurCol,CurR0w+l]<>'|') then begin paPlayer.Top := paPlayer.Top + DefaultColWidth + GridLineWidth; inc (CurRow); end;
Отладка приложений
91
vk_Up: if (CurRow>0) and (Cells[CurCol,CurRow-l]<>'I') then begin paPlayer.Top := paPlayer.Top DefaultColWidth GridLineWidth; dec(CurRow); end; end; if Cells[CurCol, CurRow] = 'X' then MessageDlg('Поздравляем!' + #13 + 'Вы вышли из лабиринта. ' ,mtlnforn\ation, [mbok] , 0) ; end;
В этой процедуре вначале выполняется проверка кода нажатой клавиши при помощи оператора ветвления сазе. Затем для каждой из четырех клавиш управления курсором (стрелка вправо, стрелка влево, стрелка вниз или стрелка вверх) выполняется проверка возможности перемещения "фишки" на соответствующую позицию. Если следующая позиция в направлении нажатой клавиши не занята стеной лабиринта и не находится за пределами игрового поля, тогда фишка перемещается, и значение переменной Cur Col или CurRow увеличивается или уменьшается на 1. Если после перемещения новая позиция соответствует точке выхода из лабиринта Рис. 2.15. Найден выход из (ячейка содержит символ "X"), то на экран лабиринта выводится сообщение с поздравлением о выходе из лабиринта (рис. 2.15). Вот и все! Сохраните проект, запустите приложение и насладитесь игрой. W Полный исходный код файла проекта, а также модулей MazeMain.pas и MazeNew.pas находится на прилагаемой К книге дискете.
Отладка приложений В состав Delphi 7 входят мощные встроенные средства отладки приложений. Для того чтобы воспользоваться этими средствами необходимо при компиляции установить флажки в разделе Debugging на вкладке Compiling окна Project Options. « Настройка параметров компилятора подробно рассмотрена в разделе "Вкладка Compiler" этой главы.
92
Глава 2. Приложение "Лабиринт"
При помощи встроенных средств отладки можно управлять процессом выполнения приложения, а также просматривать значения переменных и свойств. Программный код можно проверять построчно, исследуя на каждом шаге состояние приложения. Для того чтобы активизировать встроенные срелства отладки, выполните команду Tools >- Debugger Options, в появившемся на экране окне установите флажок Integrated (Встроенный) и щелкните мышью на кнопке ОК.
Переход в режим отладки Для того чтобы перейти в режим отладки, можно воспользоваться одной из команд меню Run, перечисленных в табл. 2.11. Таблица 2.11. Команды перехода в режим отладки Команда
Комбинация клавиш
Описание
Run
Если в исходном коде установлена точка прерывания (рассматривается в следующем разделе), то работа приложения останавливается на соответствующей строке. Построчная отладка без захода в процедуры.
*
Step Over
Trace Into
Run to Cursor
Построчная отладка с заходом в процедуры. Работа приложения останавливается на строке, в которой до его запуска был расположен курсор.
Для того чтобы выйти из режима отладки можно либо убрать все точки прерывания и нажать клавишу (команда Run >- Run), либо нажать комбинацию клавиш (команда Run >• Program Reset). В первом случае работа приложения продолжится, а во втором — прервется. г
Точки прерывания Точка прерывания (breakpoint) — это строка исходного кода, на которой работа приложения останавливается и происходит переход в режим отладки. Точек прерывания может быть столько же, сколько строк выполняемого исходного кода. Если после остановки приложения на точке прерывания нажать клавишу . то работа приложения продолжится до следующей точки прерывания (или до завершения приложения), если же нажать клавишу или , то начнется построчная отладка приложения. Установить точку прерывания в текущей строке можно одним из четырех способов. • Выполнить команду Run >• Add Breakpoint >• Source Breakpoint. • Щелкнуть правой кнопкой мыши и выполнить команду Debug >- Toggle Breakpoint раскрывшегося контекстного меню.
Отладка приложений
93
- Нажать клавишу . • Напротив строки дважды щелкнуть мышью в сером поле, расположенном вдоль левого края редактора исходного кода. По умолчанию строка с точкой прерывания выделяется красным цветом, однако цветовые настройки можно изменить на вкладке Color окна Editor Properties (команда Tools >- Editor Options). Убрать точку прерывания можно, выполнив один из выше перечисленных способов, кроме первого. В качестве упражнения установим точку прерывания в процедуре LoadMaze, реализованной в модуле MazeMain.pas. 4» См. раздел "Загрузка лабиринта в игровое поле и в редактор лабиринтов" этой главы. Для этого выполните следующие действия. 1. Перейдите в редактор исходного кода. • 2. Найдите реализацию процедуры LoadMaze. 3. Установите курсор в первой строке после ключевого слова begin. СОВЕТ Для быстрого поиска методов и переменных можно воспользоваться окном Code Explorer, которое по умолчанию расположено слева от редактора исходного кода. В этом окне в виде древовидной иерархии представлены все структурные элементы модуля, открытого в данный момент в редакторе исходного кода. Например, для быстрого перехода к процедуре LoadMaze, необходимо вначале раскрыть в окне Code Explorer корневой элемент TfmMain, а затем — элемент Private (процедура LoadMaze объявлена как закрытая). В результате будут отображены элементы, объявленные в разделе private, среди которых есть и искомая процедура LoadMaze. Для того чтобы перейти к исходному коду этой процедуры, необходимо дважды щелкнуть мышью на соответствующем элементе в окне Code Explorer. 4. Установите точку прерывания одним из четырех описанных выше способов, быстрее всего это сделать нажатием клавиши . 5. Запустите приложение и выполните команду Лабиринт >- Открыть. В результате приложение будет переведено в режим отладки, где текущая позиция в исходном коде будет отображена при помощи зеленой стрелки. 6. Нажмите клавишу , чтобы перейти к следующей строке программы, например, как показано на рис. 2.16.
Окна отладчика Всего в версии Delphi Enterprise существует девять окон отладчика, доступ к которым можно получить по команде View >- Debug Windows или при помощи специальных комбинаций клавиш. Описание окон отладчика с указанием соответствующих комбинаций клавиш представлено в табл. 2.12.
94
Глава 2. Приложение "Лабиринт"
т Мо^рм.лп п.--,--
i
= /J Private procedure TfmHain.Loadllase [sgFocLoaiS: TStrinsjCtid; focGane: b o o l e a n j j ^ -:"* DjrCol <ar F: Text-File; 6 CurRo» i: integer; •Э EnwCota | .tring; SRo«: .^ EiitlTolffl j -j. begin
ij
if od l.Execute then "*' U Vsiables/Consranli > , •v LiS Utes
begin if
1
then Caption :• 'Лаоирият - f + ExtractFileHame(od_l.FileMameJ * ' J ' elwe firWewUflae. Caption := 'Редактирование лабиринта - [ ' +• EKtractFlleNane(Od_4.FlleHejne) + ' ) ' ; HgForLoad.PoeCounc := 1: AasignFilalF,od_l.FiIeNs»e); Reset(F»; Beadln(F,BaIlColoc); -while not EOF(F) begin
do
seForLpad.CDlCount :=• length! for i := 1 to lengchfaRov) do
——- -
"""Ж)(иГ"
Рис. 2.16. Режим отладки Таблица 2.12. Окна отладчика для версии Delphi Enterprise Окно
Комбинация клавиш
Описание
Breakpoints
В этом окне перечислены все точки прерывания, установленные в приложении. Если дважды щелкнуть мышью на каком-либо пункте в списке точек прерывания, то курсор перейдет к соответствующей строке исходного кода.
Call Stack
Стек вызовов. В этом окне указывается последовательность вызовов методов и процедур, которая предшествовала текущему методу или процедуре.
Watches
Это окно при отладке просто незаменимо. В нем можно просматривать текущие значения переменных и свойств. Для того чтобы добавить какое-либо значение в список окна Watches прямо в редакторе исходного кода, можно установить курсор на переменной или свойстве и нажать комбинацию клавиш или выполнить команду Debug >- Add Watch at Cursor контекстного меню редактора исходного кода.
-
Что дальше?
95
Окончание таблицы 2.12 Окно
Комбинация клавиш
Описание
Еще одно очень полезное окно. В нем указываются текущие значения всех локальных переменных для данного метода или процедуры.
Отображает состояние используемых в приложении потоков. Что такое потоки подробно объяснять не буду, так как это очень обширная тема. Упрощенно можно сказать, что поток — это независимый процесс.
Modules
В этом окне отображается перечень используемых в данный момент программных модулей и библиотек.
Event Log
Журнал системных событий.
CPU
Это окно "по зубам" только опытным системным программистам и знатокам ассемблера, потому что в нем программный код отображается на уровне машинных команд и двоичных кодов. Здесь можно также просмотреть состояние регистров центрального процессора. Короче говоря, это — инструментарий хакера.
FPU
Это окно позволяет просмотреть состояние модуля обработки чисел с плавающей запятой, входящего в состав центрального процессора.
Local Variables Thread Status
СОВЕТ Находясь в режиме отладки, откройте поочередно каждое т этих окон и исследуйте их возможности. • Выйдите из режима отладки, нажав клавишу <Р9>, чтобы продолжить работу, или комбинацию клавиш , чтобы прервать работу приложения. •
Что дальше? В этой главе был рассмотрен процесс разработки приложения на примере игровой программы "Лабиринт". Кроме того, было объяснено, как установить параметры проекта, и как использовать встроенные средства отладки Delphi 7. Вторая часть это книги посвящена вопросам проектирования баз данных и разработке приложений управления базами данных.
-
.
. -
-
•/ .
"
•
И
1
'
ЧАСТЬ • •
DELPHI 7 и БАЗЫ ДАННЫХ Delphi 7 обладает всеми средствами создания приложений, при помощи которых пользователи могут получать доступ к информации, хранящейся в базах данных (database). База данных — это структурированная совокупность данных различных типов. Понятие базы данных очень обширное — к нему можно отнести даже совокупность обычных текстовых файлов, информация в которых хранится в определенном формате. В качестве простого примера базы данных можно также привести список сотрудников, состоящий из трех столбцов — фамилии, имени и отчества, хранимый в виде электронной таблицы (например, в формате Microsoft Excel). Тем не менее, применительно к разработке приложений, речь идет обычно о специализированных форматах баз данных, для которых могут быть определены правила хранения и обработки данных. В некоторых таких форматах (например, dBase) можно определить только структуру хранения данных, в других же (например, Paradox) — на данные можно наложить дополнительные ограничения. Существуют также такие форматы, для которых возможно определить не только структуру и правила хранения, но и правила обработки данных. К таким форматам можно отнести InterBase, Oracle и др. Кроме того, специализированные форматы обеспечивают совместный доступ к хранимым данным из различных приложений. С точки зрения архитектуры, базы данных делятся на две категории. • Локальные базы данных. Размещаются на локальном диске компьютера или в локальной сети. При совместном обращении к ним нескольких пользователей, для организации механизма блокировки доступа, используется файловая система. К таким базам данных относятся Paradox, dBase и FoxPro. Приложения, взаимодействующие с локальными базами данных, называются одноуровневыми, потому что
98
Часть II. Delphi 7 и базы данных
они находятся в одной файловой системе с базой данных. Разработке приложений, обеспечивающих доступ к таким базам данных, посвящена глава 3. • Серверы баз данных. Такие базы данных могут размещаться на отдельном компьютере (иногда они даже распределены между несколькими серверами). Серверы баз данных отличаются друг от друга способом хранения информации, однако все они для организации взаимодействия с пользователями используют один обобщенный язык, называемый SQL (Structured Query Language — язык структурированных запросов). По этой причине серверы баз данных также называют SQL-серверами. Приложения, подключающиеся к SQL-серверам, называются многоуровневыми, потому что такие приложения и базы данных могут функционировать в различных системах (на различных уровнях). Кроме основного набора команд SQL, большинство серверов баз данных используют дополнительные команды. В результате каждому серверу соответствует собственный "диалект" языка SQL (основы языка SQL рассматриваются в главе 4).
ПРИМЕЧАНИЕ
.
Язык SQL применяется также и при работе с локальными базами данных, однако при этом SQL-команды выполняются гораздо медленнее, чем в случае с SQL-серверами (особенно при обработке больших объемов данных}. Это объясняется тем, что серверы баз данных выполняют анализ SQL-команд и определяют оптимальный способ обработки или выбора данных, а в случае с локальными базами данных подобный анализ SQL-команд не выполняется. По этой причине при разработке приложений, ориентированных на работу с базами данных Paradox, dBase и FoxPro, язык SQL используется только в тех случаях, когда другие способы реализации поставленной программной задачи менее эффективны, чем использование SQL. В отличие от локальных баз данных, работа с SQL-серверами полностью основана на использовании команд языка SQL. » Более подробно язык SQL рассматривается в главах 4 и 5.
К SQL-серверам относятся, например, такие серверы: InterBase, Oracle, Sybase, Informix, Microsoft SQL Server и DB2. В этой книге рассматривается сервер баз данных InterBase (версия 6.5). Этот SQL-сервер входит в комплект поставки Delphi 7 (версии Professional и Enterprise). Созданию баз данных средствами InterBase посвящена глава 5, а разработке приложений Delphi 7 для взаимодействия с SQL-серверами — глава 6. В главе 7 будет разработано приложение управления базой данных сотрудников предприятия "Персонал", построенное на основе SQL-сервера InterBase 6.5. В завершение этой части, в главах 8 и 9 будут рассмотрены некоторые вопросы администрирования и оптимизации баз данных.
Выбор типа базы данных Вопрос выбора типа базы данных является очень важным, так как от этого зависит производительность приложения и качество его работы. •
Выбор типа базы данных
99
НЕМНОГО ТЕОРИИ Прежде, чем двигаться дальше, дадим определение таким основополагающим терминам, как таблица, строка и столбец. Таблица (table) — это набор строк в базе данных, объединенных по некоторому критерию. Например, одна таблица может использоваться для хранения информации о заказах, а вторая — о клиентах. Строка (row) — это одна запись таблицы. Например, в одной строке таблицы заказов может храниться информация об одном заказе. Столбец (column) — это поле (или атрибут), которое содержится s строке таблицы. Например, в таблице заказов может быть столбец с номерами заказов и столбец с фамилиями клиентов. Таким образом, можно сказать, что строка такой таблицы содержит поле номера и поле фамилии. При выборе типа базы данных следует учитывать такие факторы. Количество пользователей, одновременно подключенных к базе данных. SQL-серверы ориентированы на одновременное подключение многих пользователей к таблицам базы данных и потому обеспечивают механизм транзакций (» рассматривается в главе 4 в разделе "Команды COMMIT и ROLLBACK", в главе 7 в разделе "Удаление данных о сотруднике" и в главе 9 г разделе "Оптимизация приложений"). В отличие от них, большинство локальных таблиц поддерживают только механизм блокировки, основанный на использовании файловой системы. Учитывая это, использовать локальные базы данных при большом количестве пользователей нежелательно. • Объем хранимых данных. Таблицы SQL-серверов могут хранить больше данных, чем локальные таблицы. Кроме того, локальные таблицы очень больших размеров (содержащие десятки тысяч строк) более подвержены сбоям и нарушениям в структуре хранимой информации. • Необходимость использования средств администрирования. Для поддержки локальных баз данных требуется меньше ресурсов, чем для администрирования SQL-серверов. Кроме того, их проще сопровождать. • Потребность в надежной системе безопасности. Базы данных часто содержат конфиденциальную информацию, которая требует применения средств защиты. Локальные базы данных, подобные Paradox и dBASE, обеспечивают защиту только на уровне таблицы или поля. В отличие от них, большинство SQLсерверов применяют защиту на уровне всей базы данных. В этом случае права доступа к таблицам определяются при обращении пользователя к базе данных по его имени и паролю.
•
Таким образом, локальные базы данных являются наилучшим выбором, когда работа с базами данных отвечает следующим условиям. . Таблицы базы данных не будут содержать десятки тысяч строк информации. • К базе данных будет подключаться небольшое количество пользователей. • Когда не требуется применять сложных средств администрирования и сопровождения базы данных, а также, когда не требуется обеспечивать безопасность базы данных.
Глава 3
Работа с локальными базами данных Эта глава посвящена работе с локальными базами данных, так как на их примере проще изучить основы теории баз данных. При этом сразу надо отметить, что внутренняя организация одноуровневых и многоуровневых приложений имеет существенные различия, а подходы, применимые в работе с локальными базами данных очень отличаются от подходов при работе с SQLсерверами. В этой главе будет рассмотрена разработка приложений, использующих локальные таблицы Paradox.
Доступ к локальным базам данных в Delphi Приложения, созданные с помощью Delphi, обращаются к локальным базам данных через систему Borland Database Engine (BDE). Система BDE устанавливается вместе с Delphi. Через BDE можно организовать доступ и к SQLсерверам, но в Delphi 7 для этого лучше использовать новые специализированные средства (например, dbExpress — будет рассмотрено в главе 6). В тех приложениях, которые ориентированы на работу с локальными таблицами, используются компоненты с вкладки BDE, перечисленные в табл. 3.1. НЕМНОГО ТЕОРИИ В табл. 3.1 используется понятие "запрос". Его определение вытекает из его названия. Запрос (query) — это команда на языке SQL, которая передается в базу данных с целью извлечения специфической информации или внесения изменений. По той причине, что запросы основаны на использовании языка SQL, они часто называются SQL-запросами. Запросы могут возвращать наборы строк, которые еще называют наборами данных (data set). Такие запросы формируются при помощи команды SQL select. Существуют также запросы, которые используются для создания различных элементов базы данных (команды SQL, которые начинаются со слова create), для изменения данных (команда SQL update), для вставки строк в таблицы (команда SQL insert), для удаления записей (команда SQL delete) и т.д. Такие запросы не возвращают наборов данных. Как уже упоминалось ранее, при работе с локальными базами данных команды SQL намного менее эффективны, чем при работе с SQL-серверами. Тем не менее, в приложениях, ориентированных на взаимодействие с покальными таблицами, SQL-запросы все-таки иногда используются, и по этой причине в Delphi реализован специальный компонент Query. » Более подробно язык SQL рассматривается в главах 4 и 5.
Доступ к локальным базам данных в Delphi
101
Таблица 3.1. Компоненты ВОЕ, которые используются в приложенияхОе1рМ 7 для работы с локальными базами данных Компонент
Описание
Table
Используется для доступа к таблицам баз данных. Для указания имени реальной таблицы используется свойство TableName. Используется для создания, выполнения и обработки локальных SQL-запросов.
Query BatchMove
Используется для копирования и перемещения таблиц.
Классы ТТаЫе и TQuery являются производными от класса TDataSet, в котором реализованы средства взаимодействия с наборами данных. Исходя из этого, таблицы и результаты запросов, использующих команду SQL select, также можно назвать наборами данных. Для связи компонентов семейства TDataSet с элементами управления, предназначенными для работы с данными в приложениях Delphi 7, используется компонент DataSource (источник данных) расположенный на вкладке Data Access. Этот компонент исполняющий роль посредника между элементами интерфейса приложения и наборами данных. Компоненты для работы с данными находятся в палитре компонентов на вкладке Data Controls. Например, компонент DBGrid используется для отображения набора данных в табличной форме, компонент DBNavigator — для перемещения по набору данных, а компонент DBEdit — для работы с отдельными полями. Описанная архитектура локальных баз данных схематически представлена на рис. 3.1.
Псевдонимы ВОЕ Обратите внимание на элемент под названием "Псевдоним BDE", представленный на рис. 3.1. Псевдоним (от английского слова alias) — это набор параметров, определяющих метод подключения средств BDE к определенной базе данных. Для создания псевдонима необходимо воспользоваться отдельным приложением BDE Administrator или средством Delphi — SQL Explorer. Для того чтобы понять смысл псевдонимов, рассмотрим следующий пример. Предположим, в каталоге c:\Mybase расположена база данных Paradox, состоящая из нескольких таблиц. В базах данных Paradox таблицы хранятся в виде отдельных файлов с расширением .db. Предположим, что в данном случае база данных состоит из двух таблиц: Tablel.db и ТаЫе2 .db. У компонента Table, который используется в приложениях Delphi для доступа к таблицам, есть свойство DatabaseName. Это свойство указывает расположение базы данных, к которой принадлежит таблица, а имя самой таблицы указывается в свойстве TableName. Если свойству DatabaseName присвоить значение C:\MyBase, а свойству TableName — значение Tablel.db, то приложение будет работать
102
Глава 3. Работа с локальными базами данных
нормально до тех пор, пока вся база данных или файл Tablel.db не будут перенесены в другой каталог. После этого придется перекомпилировать приложение с новым значением свойства Table.DatabaseName. Псевдонимы позволяют избежать подобных проблем. При помощи средств BDE Administrator или SQL Explorer можно создать псевдоним (например, МуВазе) типа Paradox, для которого параметр Path (путь) будет иметь значение С:\MyBase. После этого в приложении для свойства Table. DatabaseName можно указать значение МуВазе, и теперь при смене места расположения базы данных отпадает необходимость в изменении и перекомпиляции приложения — новый путь указывается в качестве значения параметра Path в программах BDE Administrator или SQL Explorer, Таким образом, можно назвать эти две программы средствами администрирования локальных баз данных. База данных
-
Средства В0Е
Псевдоним BDE
Приложение TQuery
ТТаЫе
Z
X TDafa Source
Z.
TDBGrid
TDBEdit
Рис. З.1. Доступ к локальным базам данных в Delphi 7
Создание приложения баз данных при помощи мастера Database Form Wizard Чтобы лучше можно было разобраться в организации доступа к локальным базам данных, давайте разработаем простое приложение управления базой данных.
Создание приложения баз данных при помощи мастера Database Form Wizard
1 03
ПРИМЕЧАНИЕ Для того чтобы использовать базу данных, рассматриваемую в этом примере, необходимо вместе с Delphi 7 установить ВОЕ и файлы примеров. Для создания приложений баз данных в Delphi 7 используется два способа. • Первый заключается в разработке приложения вручную путем размещения нужных компонентов на пустой форме и связывания их друг с другом. • Второй способ состоит в использовании мастера Database Form Wizard, который создает форму автоматически. Разработка приложения баз данных вручную будет рассмотрено в следующем разделе, а здесь будет исследована работа мастера Database Form Wizard. 1. Запустите Delphi 7. 2. Выполните команду File >- New >- Application, чтобы создать новый проект приложения. 3. Выполните команду Database >- Form Wizard. В результате на экране появится первое диалоговое окно мастера (рис. 3,2). т \Vhaf type ol f ыт do you war* (he wiz*d to create?
. E Foim Options
,
{• [Deale ajiinple f опч Г Create л Е DalaSet Options Deale a form using ITabteobpcIs Г Deale t foiriu&ng Tfiuerv o£*ds
Cance*
Рис. З.2. Выбор типа новой формы и связанного с ней набора данных В первом диалоговом окне мастера необходимо определить следующие параметры формы и связанного с ней набора данных. • Будет ли создана простая форма — переключатель Create a simple form, или будет создана форма вида "главная-под чиненная" - переключатель Create a master/detail form. • Будет ли базироваться новая форма на компоненте Table или на компоненте Query — переключатели из группы параметров DataSet Options. 4. В первом диалоговом окне мастера оставьте переключатели, выбранные по умолчанию, как показано на рис. 3.2, и щелкните мышью на кнопке Next. 5, В следующем диалоговом окне мастер нужно указать таблицу, которая будет использована для создания формы. Для начала выберите в раскрывающемся списке Drive or Alias name псевдоним DBDEMOS.
104
Глава 3. Работа с локальными базами данных
При выборе псевдонима мастер устанавливает связь с соответствующим каталогом. При выборе псевдонима DBDEMOS в списке Table name отобразится перечень таблиц базы данных, представленной в BDE этим псевдонимом (рис. 3.3). Choose a labte (о ил Nth iho torn
Рис. 3.3. Выбор таблицы, на основа которой будет создана форма
6. Выделите в списке Table name таблицу biolife.db и щелкните мышью на кнопке Next. 7. В следующем диалоговом окне будет представлен список полей таблицы biolife. Можно по одному выбрать поля, которые необходимо отобразить на форме, используя для этого кнопку >, а можно выбрать сразу все поля при помощи кнопки » (рис. 3.4). Щелкните мышью на кнопке », а затем — на кнопке Next. Database Form Wizard ж To «W fields lolbe lam, c^ each one B-I the A«lrt4eF*fchlrierKathencick(be'h>''bulto Т с chooje al fekh, dck S» "> > " bidt on Avalabto Reids
Qrdeied^elecfedField!.
id
Species Wo "afegwy 'ommcn_Nan« LengTh In Holes
Рис. З.4. Выбор полей для создаваемой формы
В следующем окне мастера форм можно выбрать один из трех вариантой расположения полей новой формы (рис. 3.5): горизонтальное — переключатель Horizontally, вертикальное — переключатель Vertically или расположить по сетке — переключатель In the grid. Оставьте выбранный по умолчанию
Создание приложения баз данных при помощи мастера Database Form Wizard
105
переключатель, соответствующий горизонтальному расположению, и щелкните мышью на кнопке Next »
How do you we* (he fields ausnged on ihefotm?
waking lowaidi Ibe bfftom righl
lion Eta lop ослчп to the bottom.
Г In a aid Piece each field wfthin Is nun column rente a and ob(ec» waking hem left to H£t*.
N84 >
О eel
Рис. 3.5. Выбор варианта расположения попей на новой форме 9. Следующее, и последнее, диалоговое окно мастера предназначено для выбора параметров создания формы (рис. 3.6). Для того чтобы создаваемая форма была главной формой приложения, то есть такой, которая при запуске приложения открывается первой, установите флажок Generate a main form. ,
Databate Funn Wizaid Yiiu have now conr^ftHht foirri detgn Dick fh* Finish buftonlo gene ate fhf new torn.
: Form Ij ens* ion
Г Ram flol,
Canctl
Рис. З.6. Последнее диалоговое окно мастера форм 10. Кроме того, в разделе Form Generation можно указать, требуется ли создать только саму форму (переключатель Form Only) или же, помимо формы, создать и модуль данных (переключатель Form and DataModule) Для данного примера выберите переключатель Form and DataModule, а затем щелкните на кнопке Finish. В результате будет создана форма с элементами доступа к таблице, а также модуль данных, с размещенными в нем компонентами Table и DataSource (рис. 3.7).
106
Глава 3. Работа с локальными базами данных
Рис. 3.7. Результат работы мастера Database Form Wizard НЕМНОГО ТЕОРИИ Модуль данных (data module) — это объект класса TDataModule, который, как и обычная форма, предназначен для размещения компонентов. Отличие модулей данных от форм заключается в том, что на них размещаются исключительно невиэуальные компоненты. Это свойство модулей данных очень удобно использовать для централизованного хранения в проекте компонентов Table и других компонентов, используемых для организации доступа к базам данных {особенно в больших приложениях). Для того чтобы добавить пустой модуль данных в проект можно воспользоваться командой File >- New >• Data Module. Это приложение используется в качестве примера, и поэтому здесь не будут изменяться имена объектов, выбранные мастером по умолчанию. 11. Запустите приложение, а при запросе на сохранение модулей и главного файла проекта примите имена, выбранные по умолчанию. По таблице biolife.db можно перемещаться при помощи компонента DBNavigator, кнопки которого расположены в верхней части формы. С его помощью можно не только перемещаться по таблице, но и добавлять, изменять и удалять из нее записи. Так, для добавления новой записи достаточно щелкнуть мышью на кнопке со знаком "плюс", а для удаления текущей записи на кнопке со знаком "минус". Для редактирования выбранной записи достаточно перейти в нужное поле и ввести соответствующий текст. Если необходимо сохранить внесенные изменения в базу данных — щелкните мышью на кнопке с изображением "галочки", если же необходимо отменить эти изменения — щелкните на кнопке с изображением перечеркивания. 12. Завершив ознакомление с формой, закройте ее, чтобы вернуться в среду Delphi.
Создание приложения баз данных при помощи мастера Database Form Wizard
1 07
Анализ новой формы Прежде, чем двигаться дальше, давайте внимательнее ознакомимся с только что созданной формой. 1. Откройте в конструкторе форм модуль данных (если при сохранении проекта вы оставили имена, выбранные по умолчанию, то модулю данных будет соответствовать файл Unit2.pas) и щелкните мышью на компоненте Tablel. 2. Нажмите клавишу , чтобы перейти в инспектор объектов. Обратите внимание на два самых важных свойства: DatabaseName и TableName. В качестве значения свойства DatabaseName указано имя псевдонима DBDEMOS, а в качестве значения свойства TableName — имя файла biolife.db3. Выделите в модуле данных компонент DataSourcel. Среди его свойств также можно выделить два ключевых свойства: DataSet и AutoEdit. В данном случае свойство DataSet содержит ссылку на компонент Tablel. Это означает, что данные, которыми компонент DataSourcel обменивается с элементами управления, адресуются объекту Tablel, связанному с конкретной таблицей базы данных. Второе важное свойство, AutoEdit, имеет значение True. Это означает, что компонент DataSourcel автоматически переключается в режим редактирования, то есть, изменения, вносимые с помощью элементов управления, могут заноситься в соответствующие записи таблицы базы данных. 4. Щелкните мышью на одном из компонентов DBEdit и откройте инспектор объектов. У каждого из компонентов DBEdit, как и у других элементов управления данными, также есть два самых важных свойства: DataSource и DataField. Свойство DataSource содержит ссылку на компонент DataSourcel. Такая связь означает, что данные, которые вводятся с помощью компонента DBEdit, отправляются в соответствующую таблицу базы данных через объект DataSourcel. Второе свойство, заслуживающее внимания, DataField, определяет поле таблицы базы данных, с которым связан элемент управления. Итак, поведем итоги. Компонент Table связывает форму с конкретной таблицей, а посредником между ним и элементами управления для работы с данными является "компонент DataSource. Такая организация позволяет быстро подключать элементы управления к различным таблицам с названиями полей — достаточно только изменить свойство DataSource. DataSet.
Настройка элементов управления данными Запустите приложение еще раз и обратите внимание на поля таблицы biolife под названиями LengthJn, Notes и Graphic: • в поле Lengthjn отображается слишком много цифр после запятой; • поле Notes — это не обычное строковое поле, а текстовое поле большого размера, так называемое, memo-поле',
108
Глава 3. Работа с локальными базами данных
• поле Graphic содержит рисунок, вместо которого на форме отображается строка "(GRAPHIC)" (рис. 3.8). Давайте устраним эти недостатки. ;; p ЗрвсинНо
СгЛзуну
9WBO JTnggeJBh
|Clt»"1 Т Гавайи Leniffilcml
SpeaatHaw Balislotde? consp>c*jn Holts
I'lMEHO]
Lenglhjn
50 СЭ93700787 ;
Grifific .„•
[(G".,
Рис. 3.8. Отображение содержимого полей Lengthjn, Notes и Graphic
Формат отображения данных 1. Закройте приложение, перейдите в модуль данных и дважды щелкните мышью на компоненте Tablel. В результате на экране появится редактор полей таблицы biolife. 2. Выделите в нем мышью поле Length_in и нажмите клавишу , чтобы открыть инспектор объектов (рис. 3.9). НЕМНОГО ТЕОРИИ
jlebtelLenglhJn
QMonConUtint
Lengthjn 10 EdUFoimal r«ld№d
IkDsli Lenglh_1n
Inite, Kejfiel* Lo*i4jCache LookupDaaSet
Для доступа к полям таблиц в Delphi испольLoc*wRe!iJFiel . зуется класс TField. Элементам списка, Marf/abe 0 "0 отображаемого в редакторе полей таблицы, J MiWalue Nane Tsblel Lenglhjn соответствуют объекты классов, производных от TField. Например, строковому полю соРис. З.9. Свойства объекта типа ответствует объект типа TStringField, a TFloatField, соответствующего целочисленному полю — объект типа TlntegerFielci Объекты полей, как и люПОЛЮ Length In бые другие объекты, обладают рядом полезных свойств и методов. Также следует отметить, что компоненты TField, размещенные на форме или в модуле данных, принадлежат этой форме или этому модулю данных, а не связанным с ними компонентам наборов данных (Table и Query).
Как видите, полю Length_ln соответствует объект типа TFloatField. Класс TFloatField используется для работы с полями, которые содержат числа с плавающей запятой. Объекты полей имеют много полезных свойств, но в данном случае нас интересует только одно из них — DisplayFormat. Это свойство
Создание приложения баз данных при помощи мастера Database Form Wizard
109
позволяет задать формат отображения данных поля в элементах управления DBEdit ИЛИ DBGrid. В Delphi для определения формата отображения числовых полей используется набор специальных символов форматирования, представленный в табл. 3.2. Таблица 3,2. Символы, которые используются для определения формата отображения числовых полей Символ
Описание
О
Указывает на позицию цифры. Если значение поля не содержит цифру в той позиции, для которой в строке форматирования указан символ "О", то в этой позиции отображается ноль. Указывает на позицию цифры, однако если в этой позиции в значении поля не стоит никакой цифры, то в ней ничего не отображается. Позиция десятичной точки. Разделитель разрядов. Отделяет тысячи от миллионов и т.д.
хх или "хх"
Отображение числа в экспоненциальной форме. После символов "Е+" в строке форматирования можно указать до четырех символов "О", определягощих количество цифр для отображения экспоненты. Символы, заключенные в одинарные или двойные кавычки, просто отображаются в значении поля. Разделяет разделы строки форматирования для положительных, отрицательных и нулевых чисел. Если строка форматирования состоит из одного раздела, тогда она применяется для всех значений. Если она состоит из двух частей, разделенных символом V, тогда первый шаблон форматирования применяется для положительных и нулевых чисел, а второй — для отрицательных. Если же строка форматирования состоит из трех разделов, тогда первому из них соответствуют положительные числа, второму — отрицательные, а третьему — нулевые.
3. Присвойте в инспекторе объектов свойству DisplayFormat объекта TablelLength_in значение #.00. Этому шаблону соответствует отображение чисел с двумя цифрами после запятой. 4. Теперь запустите приложение и убедитесь, что в компоненте DBEdit, соответствующем полю Length_in, действительно отображается два знака после запятой. 5. Закройте приложение и откройте в конструкторе форм форму Form3, на которой размешены элементы управления данными. 6. Выделите компонент DBEdit, соответствующий полю Notes (он называется EditNotes) и выполните команду Edit >- Cut или нажмите комбинацию клавиш . В результате компонент EditNotes будет удален с формы и помещен в буфер обмена Windows. 7. Нажмите клавишу <П2>, чтобы переключиться в редактор исходного кода.
110
Глава 3. Работа с локальными базами данных
8. Находясь в редакторе исходного кода, перейдите в самый конец программного модуля, расположите курсор под строкой end. и выполните команду Edit >• Paste (или нажмите комбинацию клавиш ). В результате компонент EditNotes будет вставлен в модуль исходного кода в текстовом виде: object EditNotes:
TabOrder = 6 end 9. Измените первую строку этого фрагмента, чтобы вместо типа TDBEdit был указан тип TDBMemo: object EditNotes:
TDBMemo
10. Выделите текстовый фрагмент, соответствуюEditNotes щий компоненту EditNotes, вырежьте его в Pracett» E veils 1 буфер обмена при помощи команды Edit >- Cut (). 11. Переключитесь в конструктор форм и выполни- Рис. 3.10. Теперь компонент EditNotes МОЖет те команду Paste. отображать содержимое На первый взгляд ничего не изменилось, однако memo-поля Notes в инспекторе объектов видно, что теперь компонент EditNotes имеет тип не TDBEdit, a TDBMemo, который используется для отображения многострочных текстовых memo-полей (рис. 3.10). 12. Присвойте свойству Height компонента EditNotes значение 100, а свойству Scrollbars — значение ssVertical, и запустите приложение. Как видите, теперь в поле Notes отображается текст, который можно прокручивать при помощи вертикальной полосы прокрутки (рис. 3.11), 33. Осталось только отобразить графическое содержимое поля Graphic. Закройте приложение и перейдите в конструктор форм. 14. Проделайте для компонента EditGraphic, соответствующего полю Graphic, те же операции выреза-
и
5рйоиНо
+•„„.
Category
30020
. pc*vn TriQgefhsh
£ pegs; N arm feaistraiJe: coti
cm) LengMn
50 |
19.69
Graphic
AJSOknowm^rhebig jpated Iriggeriiih.
3fir
molu^ks by crushing them *«lh pt^verfJ leeTK Th^" *e vaaoous ealerj. arid tlvers iep&l ье&пд Егк down tijggafish dei-i» beds of peal oyiteis Jonoteellhiifiih Accof(*ngloarpl07Q
Рис. 3.11. Просмотр содержимого поля Notes
1 I Приложение "Фотоальбом"
111
ния и вставки, что и для компонента EditNotes, но теперь укажите вместо типа DBEdit тип DBImage. 15. После вставки измененного компонента EditGraphic из буфера обмена в форму присвойте его свойствам Height и Width значение 250. 16. Измените размеры формы таким образом, чтобы компонент EditGraphic был виден целиком, и запустите приложение. Теперь будет отображаться и рисунок, содержащийся в поле Graphic, как показано на рис. 3.12.
ulio known at Ibe Ыд :polled Ijiggafish Inhabtl; aia red area; and [teds upon cruflaceans aid molusks by crushrg Ihem wiih powerful leeth They afe voracious eaters, and divers repot seeing Ihe clown Iriggelfiih devoiji beds d, pearl oyster! Donntflatn» rish Accurdng ю ал 167E
Рис. 3.12. Отображение рисунка, содержащегося в попе Graphic
НЕМНОГО ТЕОРИИ Для хранения графических изображений и другой двоичной информации в таблицах используется тип данных BLOB (Binary Large Object — большой двоичный объект). Этому типу данных в Delphi соответствует класс TBlobField. В нашем приложении объект поля Graphic имеет тип TGraphicField, производный от типа TBlobField. Он предназначен для работы с теми BLOB-полями, которые содержат графические изображения. В этом разделе был рассмотрен процесс создания приложения баз данных при помощи мастера Database Form Wizard, а теперь разработаем "вручную" простое приложение для работы с базами данных.
Приложение "Фотоальбом" В этом разделе будет разработано простое приложение "Фотоальбом", построенное на основе локальных таблиц формата Paradox. На примере этого при-
112
Глава 3. Работа с локальными базами данных
ложения более подробно будут рассмотрены свойства и методы компонентов, предназначенных для работы с локальными базами данных. Прежде, чем приступить к работе, проведем небольшую подготовительную работу. Создайте на жестком диске каталог, в котором будут храниться файлы этого приложения, а также каталог, в котором будут размещены файлы базы данных. Например, каталог для приложения может называться .. \PhotoAlbum, а каталог для базы данных ., \PhotoAlbum\Base. Теперь создадим псевдоним BDE, соответствующий базе данных приложения "Фотоальбом".
Настройка BDE и создание псевдонима Как уже упоминалось выше, псевдоним BDE можно создать при помощи программ BDE Administrator и SQL Explorer. Существует также возможность создавать псевдонимы при помощи специальной программы Database Desktop, которая входит в поставку Delphi и используется преимущественно для работы с таблицами локальных баз данных. Рассмотрим процесс создания псевдонима при помощи программы SQL Explorer. Но предварительно сделаем некоторые настройки при помощи средства BDE Administrator, которое можно запустить как из программы SQL Explorer, так и из меню Пуск в среде Windows. Программа BDE Administrator содержит специальные возможности настройки параметров, выбираемых по умолчанию для создаваемых псевдонимов. В частности, с его помощью можно указывать для создаваемых локальных таблиц Paradox, так называемый языковый драйвер (language driver), который отвечает за правильную кодировку текстовых данных.
Программа SQL Explorer 1. Находясь в среде Delphi, выполните команду Database тате будет запущена программа SQL Explorer (рис. 3.13).
Explore. В резуль-
2ЙвЯ Qictoneji £* Цен Орств И*
: Ё* ^\ ь"~ •. •* гШ
t-> '•• -' '
AIDisbstAIases
BsMono(D8D£MOS
Dat«betes|Dicedraji|
DetnSai JEntB50L|
- Q uaianases ± 'jftMMBMg
Type DEFAULT DRIVER ENABLE BCD PATH
f. •' : .-,.лг: '£ !Q IBLocal
*; ТГ MQis
STANDARD FW1ADOX FALSE C:\Progfm FieiVCoranon FiesVBodexi Shae*Data
r
l «Brain DBDEMQS.
Рис. 3.13. Программа SQL Explorer
Эта программа удобна не только тем, что позволят создавать псевдонимы BDE. С ее помощью можно редактировать содержимое таблиц — причем не
Приложение "Фотоальбом"
113
только локальных баз данных, но и SQL-серверов. Кроме того, не выходя из этой программы можно выполнять SQL-запросы. 2. Перейдите в программе SQL Explorer на вкладку Databases, расположенную вдоль левой стороны окна. На этой вкладке в виде древовидной структуры представлены псевдонимы BDE — в том числе и псевдоним DBDEMOS, уже рассмотренный выше в этой главе. 3. При помощи двойного щелчка мыши на псевдониме DBDEMOS или щелчка мыши на символе "+" раскройте сначала элемент DBDEMOS, а затем — его подчиненный элемент Tables. 4. Выделите среди подчиненных элементов таблицу biolife. db. В результате в правой части окна SQL Explorer появится три вкладки. На вкладке Definition содержится информация о выделенной таблице, вкладка Data позволяет работать с данными таблицы, а при помоши вкладки Enter SQL можно выполнять SQL-запросы. 5. Перейдите на вкладку Data, пролистайте сетку вправо, к полю Graphic. 6. Сначала дважды щелкните мышью на любой записи в поле Notes, а затем в поле Graphic. Это показывает, что в программе SQL Explorer можно работать как с memoполями, так и с полями типа BLOB.
Программа BDE Administrator Прежде, чем создать новый псевдоним, настроим его параметры по умолчанию. 1. Щелкните правой кнопкой мыши на корневом элементе древовидной структуры (Databases) и выполните команду BDE Administrator из раскрывшегося на экране контекстного меню или выполните одноименную команду из меню Object. В результате откроется программа BDE Administrator (рис. 3.14). |Й.ВОЕ Administialoi СЛРимк
'.
flbjecT Е* tfew Otfbra Це*з
\>s -
Е1/У1„ * Al D &abute Abases
OetiMbiot DBDEMOS
Detabaan j Cortiguahon (
DefofeoiJ
- Q Database?
- 'пЕЕШЗШ
± Jj DelaJtDD * jg IBLocal
• т; MQIS
Tn» DEFAULT DRIVER ENABLE ВСЕ PATH
STANDARD PARADOX FALSE C:\Pioaani ЯеЛСаяпоп FJesVBoland SharedWala
A
Рис. 3.14. Программа BDE Administrator
114
Глава 3. Работа с локальными базами данных
Как видно на рис. 3.14, интерфейс программы BDE Administrator очень похож на интерфейс программы SQL Explorer за тем исключением, что в BDE Administrator вкладка Configuration находится на месте вкладки Dictionary из программы SQL Explorer. 2. Перейдите на вкладку Configuration и выберите в древовидной структуре элемент Configuration >- Drivers >• Native >- PARADOX. В правой частя окна программы отобразился перечень параметров таблиц Paradox. Те из них, которые в дальнейшем нам понадобятся, перечислены в табл. 3.3. •
Таблица 3.3. Некоторые параметры таблиц Paradox Параметр
Описание
NET DIR
Каталог, в котором размещается служебный файл pdoxusrs.net, Этот файл играет важную роль при совместном доступе к таблицам Paradox через локальную сеть. На всех компьютерах, подключаемым к таблицам Paradox, значение параметра NET DIR обязательно должно указывать на один и тот же сетевой каталог, в противном случае изменения, внесенные в таблицу с одного компьютера, не будут видны для пользователей, работающих за другим компьютером. ч
LANGDRIVER
Этот параметр отвечает за вид кодировки текстовых данных в таблицах. По умолчанию здесь указано значение ,'ascii' ANSI, однако этот языковый драйвер не поддерживает символы русского алфавита. Выберите для этого параметра значение Paradox Cyrr 866,Pdox ANSI Cyrillic ИЛИ dBASE RUS cp866.
LEVEL
Этот параметр определяет формат создаваемых таблиц Paradox. По умолчанию он имеет значение 7, что соответствует формату Paradox for Windows. Рекомендуется устанавливать значение 5, которому соответствует формат Paradox 5.O.
3. Выделите в древовидной структуре элемент Configuration >• System >• INIT и присвойте параметру инициализации LANGDRIVER такое же значение, что и соответствующему параметру таблиц Paradox. 4. В завершение выделите в древовидной структуре элемент Configuration >• System >- Formats >- Date и присвойте первым трем параметрам значение TRUE, параметру MODE -- значение 1, а параметру SEPARATOR — значение . (точка). При таком значении параметров будет использоваться следующий формат даты: дд.мм.гггг. 5. Настроив все необходимые параметры, выделите корневой элемент Configuration и выполните команду Object >- Apply, чтобы сохранить изменения, внесенные в конфигурацию BDE. 6. После этого можно закрыть программу BDE Administrator
Приложение "Фотоальбом"
115
Создание псевдонима ВОЕ Создадим псевдоним BDE в программе SQL Explorer. (В программе BDE Administrator используется точно такой же процесс создания псевдонимов.)
1. Выполните команду Object >- New или одноименную команду из контекстного меню корневого элемента древовидной структуры Databases. 2. В появившемся на экране диалоговом окне New Database Alias выберите в раскрывающемся списке Database Driver Name элемент STANDARD и щелкните мышью на кнопке ОК. Псевдонимам типа STANDARD соответствуют базы данных Paradox, dBASE, FoxPro и текстовые форматированные файлы. 3. В качестве имени нового псевдонима введите PhAlbum, а затем укажите для его параметра PATH путь к созданному ранее каталогу . . \PhotoAlbum\Base. 4. Выполните команду Object >- Apply, чтобы сохранить параметры нового псевдонима. 5. Закройте программу SQL Explorer.
Постановка задачи Обычно, на этапе постановки задачи определяют следующие параметры приложения: назначение приложения, его основные функции, структура таблиц базы данных, связи между таблицами и т.д. Пусть приложение "Фотоальбом" будет выполнять следующие функции. • Создавать фотоальбомы в формате Paradox. • Открывать фотоальбомы для просмотра и редактирования. • • • • •
Таблица каждого фотоальбома должна иметь следующую структуру. Сама фотография. Уникальный идентификатор фотографии. Название категории, к которой относится фотография. Дата фотографин. Подпись к фотографии. Таким образом, таблица фотоальбома должна содержать 5 полей (столбцов).
Просмотр содержимого фотоальбома должен осуществляться в двух режимах. • По всем категориям. В этом режиме сортировка фотоальбома должна выполняться по дате фотографий. • По одной из категорий. В этом режиме отображаются только фотографии, относящиеся к указанной категории. Редактирование фотоальбома подразумевает следующие функции. • Добавление новых фотографий. • Изменение подписи уже вставленных в фотоальбом фотографий.
116 •
Глава 3. Работа с локальными базами данных
Удаление фотографий из фотоальбома.
Разработка главной формы приложения 1. Выполните команду File >• New >• Application, чтобы создать новый проект. 2. Выполните команду File >- Save All и укажите в качестве имени программного модуля основной формы, например — PAMain.pas, а в качестве имени главного файла проекта, например — Phfllbum.dpr. 3. Можете также выполнить команду Project >- Options и настроить на свое усмотрение параметры проекта. и
Настройка проекта подробно рассматривается в разделе "Параметры проекта" главы 2.
4. Присвойте свойствам формы приложения значения в соответствии с табл. 3.4. Таблица 3.4. Свойства главной формы приложения "Фотоальбом" Свойство
Значение
Пояснение
Caption
Фотоальбом
Name
fraMain
Position
poDefault
WindowState
wsMaximized
Заголовок Префикс fm — сокращение от "Form", a "main" означает "главная". Положение выбирается по умолчанию. При инициализации форма разворачивается на весь экран.
5. Расположите на форме компонент MainManu с вкладки Standard и компонент ImageList с вкладки Win32 палитры компонентов. 6. Присвойте свойству Нате компонента ImageList значение ilMain и дважды щелкните мышью на нем. 7. В редакторе компонента ImageList при помощи кнопки Add добавьте в список следующие пиктограммы из каталога Buttons (по умолчанию расположен в каталоге \Prograra Files\Common Files \Borland SharedX linages): doorshut.bmp, edit.bmp, export.bmp, filenew.bmp, fldropen.bmp и import.bmp. Для каждой пиктограммы будет выдан запрос о создании второго изображения, соответствующего неактивному состоянию кнопки. Ответьте на каждый такой запрос утвердительно, а затем можете удалить вторые изображения пиктограмм для неактивного состояния кнопки при помощи кнопки Delete. «
8.
Работа с этим компонентом подробно рассмотрена в разделе "Компонент ImageList" главы 2.
Присвойте свойству Name компонента MainMenu значение mmMain, а свойству Images — ссылку на компонент ilMain. 9. Дважды щелкните мышью на компоненте mmMain и в редакторе меню создайте два меню: Альбом (Name- = mmiAlbum) и Фотографии (Name = mmiPhotos). Команды этих меню перечислены в табл. 3.5. и табл. 3.6.
Приложение "Фотоальбом"
117
Таблица 3.5. Команды меню Альбом Команды (свойство
Свойство
Значение
Caption)
Создать
1
Name
mraiNewAlbum
Image Index
Номер пиктограммы filenew.bmp в списке ilMain.
Name
mmi Op e nAlbum
Imagelndex
Номер пиктограммы fidropen.bmp в сп иске ilMain,
-
Name
Оставьте значение, выбранное по умолчанию.
Выход
Name
mmiExit
Imagelndex
Номер пиктограммы doorshulbmp в списке ilMain.
Shortcut
Alt+X
Открыть
Таблица 3.6, Команды меню Фотографии Команды (свойство
Свойство
Значение .
Caption) Добавить Переименовать Удалить
Name
mmiNewPhoto
Imagelndex
Номер пиктограммы import.bmp в списке ilMain.
Name
mmi Rename Phot о
Imagelndex
Номер пиктограммь edit . bmp 8 списке ilMain.
Name
mmi Del Photo
Imagelndex
Номер пиктограммы export.bmp в списке ilMain.
10. Кроме того, присвойте свойству Visible компонента mmiPhotos (меню Фотографии) значение False. Это меню должно отображаться только после того, как. будет выполнена команда Альбом >- Открыть. « См, подробнее в разделе "Разработка меню" главы 2.
1 i . Расположите на форме компонент Panel с вкладки Standard палитры компонентов, и присвойте его свойству Name значение paAlbum, а остальным свойствам — значения согласно табл. 3.7. Таблица 3.7. Свойства компонента paAlbum Свойство
Значение
Пояснение
Align
alLeft
Выравнивание вдоль левого края формы.
BevelOuter
bvLowered
Внешняя фаска углубленная.
157
Заголовка нет. Ширина.
Caption Width
118
Глава 3. Работа с локальными базами данных
12. Расположите на панели paAlbum компоненты из панели компонентов (см. рис. 1.5) согласно табл. 3.8. Таблица 3.8. Компоненты, размещенные на панели paAlbum Свойство
Значение
Пояснение
Компонент Label с вкладки Standard Align
alTop
Выравнивание вдоль верхнего края панели.
Alignment
taCenter
Выравнивание текста по центру.
Caption
Выбор альбома
Текст, отображаемый компонентом
Color
clNavy
Цвет фона.
Font. Color
clWhite
Цвет шрифта.
Font. Style
[fsBold]
Name
laAlbumCap
Полужирный шрифт. Префикс la — сокращение от "Label", "album" означает "альбом", а "cap" — сокращение от слова "caption" {"заголовок").
Label.
•
Компонент ToolBar с вкладки Win32 Align
alTop
AutoSize
True
Выравнивание вдоль верхнего края панели. Высота панели инструментов изменяется автоматически в соответствии с высотой кнопок.
ButtonWidth
51
Ширина кнопок.
EdgeBorders
[ebTop, ebBottom]
Рамки вдоль верхнего и нижнего края.
Images
ilMain
Name
tlbAlbum
ShowCaptions
True
На кнопках, кроме пиктограмм, также будут отображаться и надписи.
Компонент FileListBox с вкладки Win 3.1. Этот компонент предназначен для отображения списка файлов в заданном каталоге. Align
alClient
Заполняет все свободное пространство панели.
ExtendedSelect
False
Запрет на выделение нескольких файлов в списке.
Mask
*.db
Будет отображать только файлы с расширением .db
Name
flbAlbum
Приложение "Фотоальбом"
119
13. Щелкните правой кнопкой мыши на компоненте tlbAlbum. При помощи команды New Button контекстного меню добавьте в панель инструментов три кнопки и присвойте им имена tlbtNewAlbum, tlbtOp,enAlbum и tlbtExit. 14. Присвойте свойству Menuitem каждой из кнопок соответственно значения mmiNewAlbum, mmiOpenAlbum и mmiExit. 15. Щелкните мышью на свободном пространстве формы fmMain и в инспекторе объектов присвойте свойству ActiveControl ссылку . на компонент flbAlburn. Это означает, что при инициализации формы активным будет компонент fibAlbum. На данном этапе разработки форма fmMain в конструкторе форм должна выглядеть примерно так, как показано на рис. 3.15. 1 7"т Фотоальбом
Длвбом
И
Создать
Q--.7t.-b
Выход
•
Рис. 3.15. Форма fmMain с панелью pa&lbum 16. Расположите на форме компонент Splitter с вкладки Additional и присвойте его свойству Name значение slAlbum. Этот компонент используется для создания в клиентской области формы областей переменного размера. 17. Для иллюстрации работы компонента Splitter расположите на форме несколько компонентов Panel, на которых в дальнейшем будут размещены элементы управления фотоальбомом. Описание свойств и взаимного размещения этих компонентов представлено в табл. 3.9. Таблица 3.9. Компоненты Panel Свойство
Значение
Пояснение
Name = paPhotos — на этой панели будут размещены все элементы управления, имеющие отношение к работе с фотоальбомом Заполняет все свободное пространство формы. Align alClient BevelOuter bvLowered Внешняя фаска углубленная. Панель без надписи. Caption
120
Глава 3. Работа с локальными базами данных
Окончание таблицы 3.9 Свойство
Значение
Пояснение
Name = paPhotoslD — размещается на панели paPhotos. На ней будут размещены элементы редактирования и просмотра фотоальбома Панель paPhotoslD выравнивается вдоль левого Align alLeft края панели paPhotos. BevelOuter
bvLowered
Width
268
Внешняя фаска углубленная. Ширина панели в пикселях.
Name = paPhotosCap — размещается на панели paPhotoslD. На ней будут размещена панель инструментов фотоальбома и список категорий Align Панель paPhotosCap выравнивается вдоль левого alTop края панели paPhotoslD. Панель отображается без окаймляющих фасок. BevelOuter bvNone Height
80
Высота панели в пикселях.
Name = papicture — размещается на панели paPhotos. На ней будет отображено поле подписи фотографии и само графическое изображение. Панель papicture заполняет все свободное проAlign alClient странство панели paPhotos. Внешняя фаска углубленная. BevelOuter bvLowered 18. Прежде, чем выполнить первый запуск приложения, выполните в конструкторе форм команду Альбом >• Выход и введите в обработчик события rnmiExitClick оператор Close; ' Фотоальбом
Рис. 3.16. Расположение панелей на форме fmMain
Приложение "Фотоальбом"
121
19. Сохраните проект и запустите приложение. Панели приложения должны располагаться, как показано на рис. 3.16. 20. Расположите указатель мыши над границей между компонентом f IbAlbum и панелью paPhotos. Указатель мыши примет форму двух вертикальных линий с направленными в разные стороны стрелками (см. рис. 3.16) — действие присущие компоненту Splitter. В этот момент можно нажать левую кнопку мыши и перетащить границу влево или вправо. 21. Закройте приложение. 22. В конструкторе форм выделите все панели, подчиненные панели paPhotos, и очистите свойство Caption. 23. Выберите в раскрывающемся списке инспектора объектов панель paPhotos и присвойте ее свойству Visible значение False.
Разработка структуры таблицы фотоальбома В этом приложении будут использоваться три компонента: Table и Query с вкладки ВОЕ палитры компонентов, а также компонент Datasource с вкладки Data Access. 1. Расположите на форме компонент Table, присвойте его свойству Name значение fcaAlbura, а свойству DatabaseName — значение PhAlbum. Физически этой таблицы еще не существует. Ее имя (свойство TableName) будет определяться в процессе работы приложения, сейчас же необходимо создать ее логическую структуру. На основе этой структуры в дальнейшем будут создаваться все таблицы фотоальбомов. 2. Дважды щелкните мышью на компоненте taAlbum. В результате на экране появится редактор полей. 3. Щелкните в нем правой кнопкой мыши и выполните команду New Field раскрывшегося контекстного меню. Это приведет к открытию диалогового окна New Field, (рис. 3.17). He» Field - ГйИ piujwfes
--
• —-
Name
1я«
™ .- — - —" д
•
CoropOrfffc
1
J S« т
F«d (дее- в7 J^(e
Г -Eafculaed
l Г
J ~3 OK
1 Г J^up
1 -Г Carssi
3 •; ~3 |
ЦФ
Рис. 3.17. Диалоговое окно New Field
122 Глава 3. Работа с локальными базами данных Как уже указывалось выше в разделе "Постановка задачи" этой главы, таблица фотоальбома, разрабатываемого приложения, должна состоять из пяти полей. Создадим первое из них, воспользовавшись открытым окном New Field. 4. В поле Name укажите значение ГО, в списке Туре выберите значение Autolnc, в разделе FieldType щелкните мышью на переключателе Data, а затем щелкните мышью на кнопке ОК. ПРИМЕЧАНИЕ Autolnc — это целочисленный тип, характерный для таблиц Paradox. Для каждой таблицы используется внутренний счетчик записей, который автоматически увеличивается на единицу при добавлении новой записи. Таким образом, при добавлении первой записи в таблицу в поле, которое имеет тип Autolnc, автоматически заносится 1, при добавлении второй записи — 2, и т.д. Поля типа Autolnc обычно используют для создания уникальных индексов и первичных ключей. НЕМНОГО ТЕОРИИ Рассмотрим такие понятия из теории баз данных как индекс и первичный ключ, Индекс (index) — это механизм физического хранения данных, который позволяет ускорить операции поиска строк в таблице. Использование индексов дает возможность отказаться от необходимости последовательного перебора всех строк таблицы — строки сортируются таким образом, чтобы обеспечить максимально быстрый поиск. Построение индексов в различных базах данных реализовано no-разному. Для таблиц Paradox используются первичные (primary) и вторичные (secondary) индексы. Первичные индексы построены на основе первичного ключа и физически представляют собой отдельные файлы с расширением .рх. Для построения вторичных индексов используется два файла (для одного индекса) с расширениями . xg/V и . yg/V. Первичный ключ {primary key) — это столбец (или набор столбцов), который однозначно идентифицирует каждую строку. Примером первичного ключа может быть номер счета в таблице счетов, который для каждого клиента является уникальным. 5. Аналогичным образом создайте при помощи окна New Field еще четыре поля, руководствуясь табл. ЗЛО. Следите за тем, чтобы в наборе переключателей FieldType всегда был выбран переключатель Data. Таблица 3.10. Остальные четыре поля таблицы taAlbum Поле Name
Поле Type
Поле Size
CatNarne
String
30
PhDate
Date
Title
String
Picture
Blob
100
Обратите внимание на то, что для каждого поля создается объект соответствующего типа. Благодаря этому к полям таблицы taAlbum можно обращаться по имени объекта, например:
Приложение "Фотоальбом"
123
taAlbumCatName,AsString := 'Новая к а т е г о р и я ' ;
Ту же самую операцию можно выполнить при помощи метода FieldByName: taAlbum.FieldByName('CatName').AsString
1
:= 'Новая к а т е г о р и я ;
Есть и еще один способ. К полям можно обращаться при помощи свойства Fields: t a A l b u m . F i e l d s [ 1 ] . A s S t r i n g := 'Новая категория';
Обратите внимание на то, что нумерация полей начинается с нуля. Итак, определив структуру таблицы taAlbum, изменим теперь некоторые свойства объектов полей. 6. В сетке данных на главной форме приложения должны отображаться только два поля: CatName и PhDate. Исходя из этого, выделите поочередно все остальные поля в редакторе полей и присвойте в инспекторе объектов свойству Visible значение False. 7. Осталось только указать, какие заголовки будут отображаться в сетке данных для полей CatName и PhDate. Присвойте при помощи инспектора объектов свойству taAlbumCatName.DisplayLabel значение Категория, а свойству taAlbumPhDate.DisplayLabel значение Дата. Если необходимо изменить ширину столбцов, отображаемых в сетке данных, измените значение свойства DisplayWidth. 8. Теперь определим для таблицы taftlbum индексы. Для этого выделите на форме компонент taAlbum, и в инспекторе объектов дважды щелкните мышью в поле свойства indexDef s. В результате на экране появится редактор индексов. 9. Щелкните в этом поле правой кнопкой мыши и выполните команду Add раскрывшегося контекстного меню. В результате будет создан объект индекса taAlbumlndexl. 10. Перейдите в инспектор объектов, присвойте свойству Fields значение ID, a свойству Options —значение [ixPrimary, ixUnique]. Итак, приведенным выше способом, был определен первичный уникальный индекс по полю ID. 11. Создайте аналогичным образом еще два объекта индекса. Для индекса taAlbumlndex2 присвойте свойству Fields значение CatName; PhDate, а для индекса taAlbumIndex3 -- значение PhDate;CatName. Обратите внимание, что разделителем является точка с запятой. Такое определение свойств индексов означает, что при выборе индекса taftlbuiuindex2 сортировка выполняется сначала по категории, а затем — по дате, а при выборе индекса taAlbumindex3 — наоборот.
Компоненты DataSource и Query Теперь расположите на форме компонент DataSource с вкладки Data Access. Как уже упоминалось ранее, он исполняет роль посредника между ком-
124
Глава 3. Работа с локальными базами данных
понентами наборов данных и элементами управления. Присвойте его свойству Нате значение dsAlbum, а свойству DataSet — значение taAlbum. Расположите также на форме компонент Query с вкладки ВОЕ. Он будет использоваться для формирования списка категорий, вставленных в таблицу taAlbum. Присвойте его свойству Нагое значение quCategories, а свойству DatabaseName — значение PhAlbum.
Компоненты для редактирования и просмотра данных фотоальбома 1. Расположите компоненты на панелях paPhotosCap, paPhotoslD и paPicture, руководствуясь табл. 3.11, 3.12 и 3.13. 2. Щелкните правой кнопкой мыши на компоненте tlbPhotos. При помощи команды New Button контекстного меню добавьте на панель инструментов три кнопки и присвойте им имена tlbtNewPhoto, tlbt Rename Photo и tlbtDelPhoto. Таблица 3-11. Компоненты, расположенные на панели paPhotosCap Свойство
Значение
Пояснение
Компонент Label с вкладки Standard Align
alTop
Выравнивание вдоль верхнего края панели.
Alignment
taCenter
Выравнивание текста по центру.
Caption
Фотографии
Color
clHavy
Font. Color
с 1 White
Текст, отображаемый компонентом Label. Цвет фона. Цвет шрифта.
Font. Style
[fsBold]
Name
laPhotosCap
Полужирный шрифт. Префикс la — сокращение от "Label', "photos" означает "фотографии", a "cap" — сокращение слова "caption" ("заголовок").
Компонент ToolBar с вкладки Win32 Align
alTop
AutoSize
True
ButtonWidth
88
Images
ilMain
Harne
tlbPhotos
Выравнивание вдоль верхнего края панели. Высота панели инструментов изменяется автоматически в соответствии с высотой кнопок. Ширина кнопок. Ссылка на компонент imageList, содержащий пиктограммы кнопок панели инструментов. Префикс tlb — сокращение от "ToolBar".
Приложение "Фотоальбом"
125
Окончание таблицы 3.11 Свойство
Значение
Пояснение
ShowCaptions
True
На кнопках кроме пиктограмм будут также отображаться и надписи.
3. Щелкните правой кнопкой мыши на компоненте tlbPhotos. При помощи команды New Button контекстного меню добавьте в панель инструментов три кнопки и присвойте им имена tlbtNewPhoto, tlbtRenamePhoto и tlbtDelPhoto. Присвойте свойству Menultem каждой из кнопок значения rnmiNewPhoto, mmiRenamePhoto И mrniDelPhoto соответственно. Еще один компонент Label с вкладки Standard Caption
Категория:
Left
8
Name
laCategory
Отступ надписи "Категория" от левого края панели paPhotosCap.
Отступ надписи "Категория" от верхнего края панели paPhotosCap. Компонент ComboBox с вкладки Standard
Top
64
Left
68
Name
cbCategories
Style
csDropDownList
Top
58
Width
197
Значения нельзя ввести в текстовом поле, а можно только выбрать из раскрывающегося списка. Ширина раскрывающегося списка.
Таблица 3.12. Компоненты, размещенные на панели paPhotoslD Свойство
Значение
Пояснение
Компонент StatusBar с вкладки Win32 — используется в приложениях в качестве строки состояния. Name
stPhotos
Компонент DBNavigator с вкладки Data Controls — будет использоваться для перемещения по таблице фотоальбома. Align alBottom Компонент располагается вдоль нижнего края панели paPhotoslD. DataSource Подключение dsAlbum к таблице taAlbum
Name
dna Photos
. 126
Глава 3. Работа с локальными базами данных
Окончание таблицы 3.12 Свойство
Значение
Пояснение
VisibleButtons
[nbFirst, nb Prior,
Будут использоваться только первые четыре кнопки навигации: перемещение на первую запись таблицы, перемещение на предыдущую запись таблицы, перемещение на следующую запись таблицы и перемещение на последнюю запись таблицы.
nbNext,nbLast]
-
Компонент DBGrid с вкладки Data Controls — будет использоваться для отображения данных о категории и дате фотографий alClient
Сетка данных занимает все свободное пространство панели paPhotosID.
DataSource
dsAlbum
Сетка подключена к источнику данных dsAlbum.
Name
dgrPhotos
Options
[dgTitles, dgColLines,
Align
/
dgRowLines,dgTabs,
Все остальные параметры отключите (значение False).
dgRowSelect,
•*
dgAlwaysShowSelection, dgCancelOnExit]
Readonly
В сетке нельзя редактировать данные и удалять строки таблицы.
True
Таблица 3.13. Компоненты, размещенные на панели paPicture Свойство
Значение
Пояснение
Компонент DBMemo с вкладки Data Controls — используется для отображения содержимого текстовых полей (строковых или memo). Align
alTop
Компонент располагается вдоль верхнего края Панели paPicture.
Цвет memo-поля соответствует цвету полос прокрутки.
Color
clScrollBar
DataSource
dsAlbum
Подключение к источнику данных dsAlbum.
DataField
Title
Отображает значение поля Title (подпись фотографии).
Font. Style
[fsBold]
Выделяем текст полужирным шрифтом.
Приложение "Фотоальбом"
127
Окончание таблицы 3.13 Свойство
Значение
Height
52
Name
dmeTitle
Пояснение
Только для просмотра данных. Компонент DBlmage с вкладки Data Controls — используется для отображения содержимого графических blob-полей. Readonly
True
Align
alClient
Color
clScrollBar
DataSource
ds Album
DataField
Picture
Name
dimPhoto
Название поля таблицы, в котором хранятся изображения.
4. Присвойте свойству Menultem каждой из кнопок соответственно значения mmiNewPhoto, mmiRenamePhoto И mmiDelPhoto. Теперь форма fmMain в режиме конструктора должна выглядеть так, как показано на рис. 3.18.
Рис. 3.18. Форма fmMain в режиме конструктора 5. Сохраните проект и запустите приложение. 6. Что можно увидеть в этом приложении? Практически ничего. Панель paPhotos не отображается, а из видимых элементов управления работает только команда Альбом >- Выход и соответствующая ей кнопка панели инструментов. 7. Закройте приложение и вернитесь в конструктор форм.
128
Глава 3. Работа с локальными базами данных
Программный код главной формы Первое, что нужно сделать в приложении — это отобразить список доступных фотоальбомов (таблиц .db) в списке flbAlbum. Но для этого необходимо каким-то образом извлечь содержимое BDE-параметра PATH псевдонима PhAlbum, и присвоить это значение свойству flbAlbum. Directory. Лучше всего это сделать при создании формы fmMain. 1. Выберите в раскрывающемся списке инспектора объектов компонент fmMain. 2. Перейдите на вкладку Events и дважды щелкните мышью на поле события OnCreate. 3. Вставьте в метод FormCreate строки, выделенные в представленном ниже фрагменте полужирным шрифтом: procedure TfmMain.FormCreate(Sender: TObject); var AliasParams: TStringList; begin AliasParams := TStringList.Create; 1 Session.GetAliasParams('PhAlbum ,AliasParams); flbAlbum.Directory := copy(AliasParams[0],6,length(AliasParams[0])-5); Aliasparams.Free; flbAlbum.Update; end;
Для доступа к псевдонимам BDE используется объект Session типа TSession. Объекты этого типа создаются и уничтожаются приложением автоматически, и используются для работы с базами данных. В частности в классе TSession определен метод GetAliasParams, в который в качестве первого параметра передается имя псевдонима BDE. Этот метод возвращает значения параметров псевдонима в виде набора строк. Этот набор строк затем сохраняется во втором параметре метода, имеющем тип TStringList. В качестве второго параметра метода GetAliasParams МОЖНО указывать свойства TMemo. Lines, TListBox. Items и т.п. В данном случае лучше создать объект типа TStringList при помощи метода Create, а затем сразу удалить его при помощи метода Free. Набор строк, возвращаемый в переменную AliasParams, представляет собой пары вида ПАРАМЕТР=ЗНАЧЕНИЕ, причем параметр PATH следует в списке первым. Поэтому при помощи функции сору можно извлечь подстроку, которая начинает после символа "=" (пятый по счету). Этой подстроке соответствует каталог, указанный в параметре PATH псевдонима PhAlbum. На данной стадии разработки приложения в этом каталоге еще ничего нет, поэтому при запуске приложения список flbAlbum будет пуст. Тем не менее, если бы там уже были таблицы фотоальбомов, то после изменения значения свойства Directory было бы необходимо обновить содержимое списка. Именно для этой цели и используется метод Update.
Приложение "Фотоальбом"
129
Если на этом шаге разработки запустить приложение, то станет очевидной одна логическая ошибка. Команда Альбом >- Открыть доступна, хотя список альбомов пуст1. Устраним этот недостаток. Логично предположить, что соответствующую проверку лучше всего выполнять в тот момент, когда проявляются какие-либо изменения в списке f IbAlbum. 4. Создайте обработчик события f IbAlbum. OnChange. Это можно сделать, дважды щелкнув мышью на компоненте f IbAlbum в конструкторе форм. 5. Вставьте в обработчик события следующий программный код: mmiOpenAlbum.Enabled := fIbAlbum.Itemlndex > -1; tlbtOpenAlbum.Enabled := mioiOpenAlbum. Enabled;
Здесь в первой строке определяется доступность команды Альбом >- Открыть в зависимости от количества элементов в списке f IbAlbum. Если этот список пуст (flbAlbum. Itemlndex = -1), тогда команда меню будет недоступна. Затем на основании значения свойства Enabled команды меню устанавливается значение свойства Enabled соответствующей кнопки панели инструментов. 6. По ходу дела вставьте в метод f IbAlbumChange три оператора: taAlbum.Close; paPhotos.Visible := False; mmiPhotos.Visible := False; Метод f IbAlbumChange вызывается не только при добавлении в список flbAlbum новых элементов, но и при смене текущего выбранного элемента, то есть, выборе другого фотоальбома. При этом открытый в данный момент фотоальбом должен закрыться (taAlbum.close), а панель paPhotos с компонентами, предназначенными для работы с альбомом, и меню Фотографии должны стать невидимыми. 7. Сохраните проект и запустите приложение. Теперь команда Открыть должна быть недоступной, так как на этой стадии разработки приложения в списке нет ни одного фотоальбома. 8. Закройте приложение и вернитесь в конструктор форм.
Создание нового альбома В этом разделе будет решена задача создания нового альбома по команде Альбом >- Создать. 1. Щелкните мышью на этой команде в конструкторе форм, чтобы создать обработчик события mmiNewAlbum.OnClick. 2. Внесите в этот метод следующие изменения: procedure TfmMain.mmiNewAlbumClick(Sender: TObject); var AlbumName: string; begin while InputQuery{'Создать, альбом', 'Укажите название'
13О
Глава 3. Работа с локальными базами данных
AlbumName) do with taAlbum do . B begin Close; TableName := AlbumName; if Exists then MesaageDlg('Такой альбом уже существует!', mtError,[mbok],0) else begin CreateTable; f IbAlbum. Update ; if pos('.db',TableName) = 0 then TableName := TableName + ' . d b ' ; fIbAlbum.IternIndex := flbAlbum.Items,IndexOf(TableName); flbAlbumChange(Self); break; end; end;
end;
Функция inputQuery отображает на экране запрос на ввод текстовой строки. Если при этом пользователь выбирает кнопку ОК, то функция возвращает значение True, в противном случае возвращается значение False. Введенная строка сохраняется в строковой переменной, в данном случае — AlbumName. Цикл while позволяет выводить запрос до тех пор, пока пользователь не выберет кнопку Cancel (Отметить) или не нажмет клавишу <Escape>. Тем не менее, в конце процесса создания таблицы выполняется принудительный выход из этого цикла при помощи процедуры break. После того, как пользователь введет название альбома и нажмет клавишу <Enter> (аналогично щелчку мыши на кнопке ОК), выполняется следующая последовательность действий. • Текущий альбом закрывается (taAlbum.Close). • Свойству таЫеЫате компонента taAlbum присваивается новое значение. • Выполняется проверка наличия альбома с таким же названием в том же каталоге (if Exists). • Если проверка дает положительный результат, то пользователю выдается соответствующее сообщение, после чего опять в цикле отображается запрос на ввод названия альбома. • Если альбома с указанным названием не существует, тогда создается с- ответствуюшая таблица (taAlbum. CreateTable). Структура созданной таблицы соответствует тому набору объектов полей и индексов, который был определен ранее. Это можно было бы сделать и программно при помощи свойств TTable-. FieldDef s И TTable . IndexDef s.
Приложение "Фотоальбом"
131
• Обновляется список flbAlbum, чтобы он отобразил новую таблицу (flbAlbum. Update). • Выполняется проверка имени таблицы (свойство taAlbum.TableName). Если в конце имени не указано расширение .db, то оно добавляется. • Номер текущего выбранного элемента в списке flbAlbum определяется на основании имени только что созданной таблицы. • Принудительно вызывается метод flbAlbumChange с параметром Self. Идентификатор Self используется в качестве ссылки объекта на самого себя при вызовах методов. В данном случае он особого значения не имеет и просто указывает на то, что инициатором события OnChange, для которого вызывается метод flbAlbumChange, является форма fmMain. • После этого цикл while принудительно прерывается при помощи процедуры break. 3. Сохраните проект и запустите приложение. 4. Выполните команду Альбом >- Создать или щелкните мышью на кнопке панели инструментов, соответствующей этой команде. 5. Введите в качестве названия нового альбома Семейный и нажмите клавишу <Enter>. В результате будет создана таблица Семейный.db, что отобразится в списке flbAlbum. Теперь команда Альбом >- Открыть станет доступной (рис. 3.19).
Создать Открыть
В
1
Выход
Рис. 3.19. Создан новый фотоальбом
Можно исследовать созданные файлы, просмотрев при помощи какойнибудь специализированной программы содержимое каталога, соответствующего псевдониму PhAlbum. Также можно запустить программу SQL Explorer, и открыть в ней созданную таблицу Семейный.db.
132 Глава 3, Работа с локальными базами данных Открытие альбома Перейдем к реализации следующей функции программы "Фотоальбом" — открытие уже существующего альбома. Для этого необходимо реализовать соответствующий обработчик события. 1. Создайте обработчик события mmiOpenAlbum. Onclick и вставьте в него следующий программный кол: if mmiOperiAlburn. Enabled then begin taAlbum.TableName
Обработчик события rnmiOpenAlbum.OnClick открывает тот альбом, который в данный момент выбран в списке flbAlbum. Вначале выполняется проверка доступности команды Альбом > Открыть. Если она доступна, тогда таблице taAlburn ставится в соответствие выбранная в списке физическая таблица. После подключения таблицы она открывается (метод Open), а затем отображаются панель paPhotos и меню фотографии. В завершение работы обработчика события, курсор переходит в сетку данных dgrPhotos. 2. Сохраните проект и запустите приложение. 3. Выделите в списке альбом Семейный.db и щелкните мышью на кнопке Открыть. Уже лучше! Однако нам еще предстоит поработать. 4. Вернитесь в конструктор форм. Открытие альбома сейчас выполняется при помощи команды Альбом >• Открыть или соответствующей ей кнопки панели инструментов, но было бы удобно, если бы это можно было делать при помощи двойного щелчка мыши на соответствующем элементе в списке, а также нажатием клавиши <Enter>. 5. Выделите компонент flbAlbum, перейдите в инспекторе объектов на вкладку Events. 6. Раскройте список в поле события OnDblClick и выберите в нем ссылку на метод mmiOpenAlbumClick. Двойной щелчок мышью на каком-либо элементе в списке равнозначен выполнению команды Альбом >- Открыть. 7.. Создайте обработчик события flbAlbum.OnKeyDown и вставьте в него следующую строку программного кода: if key = vk_Return then iraniOpenAlbumClickjSelf);
Это означает следующее: если нажата клавиша <Enter>, тогда вызывается метод mmiOpenAlbumClick.
Приложение "Фотоальбом"
133
Определение набора категорий При открытии таблицы альбома необходимо формировать перечень категорий, которые используются в этом альбоме. Для этого можно выполнить простой SQL-запрос при помощи компонента Query. ПРИМЕЧАНИЕ Подробно команды языка SQL будут рассмотрены в следующих двух главах. В приложении "Фотоальбом" используется только простая SQL-команда select, которая предназначена для выбора данных о категории фотографий из таблицы фотоальбома. 1. Добавьте" в обработчик события mmiOpenAlbum.Onclick фрагмент программного кода, выделенный полужирным шрифтом: procedure TfmMain.itimiOpenAlbumClick (Sender : begin if mmiOpenAlbum.Enabled then begin
TObj ect) ;
tafilbum.TableName := fIbAlbum.FileName; taAlbura.Open,with quCategories, cbCategories do begin Close; SQL.Clear; SQL.Add('select distinct CatName from "' + taAlbum.TableName + ' " ' ) ; Open; Items.Clear; Items.Add(''); while not EOF do begin Items.Add(quCategories.FieldByNamef'Catname').AsString); Next;
Конструкция with quCategories, cbCategories do означает, ЧТО все свойства и методы, расположенные между ее ключевыми словами begin и end, относятся либо к компоненту quCategories, либо к компоненту cbCategories (или же к форме fmMain). Итак, вначале набор данных guCategories закрыва-
134
Глава 3. Работа с локальными базами данных
ется, а затем при помощи метода Clear удаляется содержимое свойства SQL. Свойство SQL — это набор строк, соответствующий SQL-команде, выполняемой при помощи метода Open (если используется SQL-команда select) или ExecSQL (во всех остальных случаях). Если был выбран альбом Семейный, то в свойство SQL добавляется команда select distinct CatName from "Семейный.db", после чего этот запрос выполняется при помощи метода Open. Представленную команду select можно прочитать так: "выбрать набор значений поля CatName из таблицы Семейный.db". При этом повторяющиеся значения должны отбрасываться -- значение слова distinct. После вызова метода Open компонент quCategories содержит набор строк, выбранных из таблицы Семейный.db (или другой таблицы фотоальбома). Теперь необходимо подготовиться к переносу списка категорий в компонент cbCategories, Для этого вначале нужно очистить содержимое свойства Items, а затем добавить в список категорий постоянный элемент <Все>. Если в раскрывающемся списке выбран элемент <Все>, то отображается все содержимое открытого фотоальбома. Затем следует цикл, в котором последовательно просматриваются все строки набора данных quCategories, и названия выбранных категорий заносятся в список cbCategories. В конце обработчика события текущим элементом списка становится первый элемент <Все>. 2. Сохраните проект и запустите приложение. 3. Откройте альбом Семейный. Как и следовало ожидать, список Категория содержит только один элемент <Все> (рис. 3.20). 4. Закройте приложение и вернитесь в конструктор форм.
Метод SetContent Теперь необходимо определить метод, в котором выполнялось бы подключение индексов и ограничение набора строк в соответствии с выбранной категорией фотографий. Для этого лучше создать отдельный метод. 1. Создайте его и назовите SetCcntent. 2. Объявите этот метод как закрытый: private { Private declarations } procedure SetContent;'
3. Затем в разделе реализации создайте сам этот метод: procedure TfmMain.SetContent; begin with taAlbum do begin
Приложение "Фотоальбом"
135
if cbCategories.Itemlndex = 0 then begin CancelRange; IndexName : = "'taAlbumlndexS'; end else begin IndexName := 'taAlbum!ndex2'; SetRange([cbCategories.Text], [cbCategories.Text]); end; First; end; end,Ж^ Фотоальбом Алвбом ^'ш о граф мы
"в
Добаоигь
Переигчмжать
Категория- |
Рис. 3.20. Сформирован список категорий Этот метод выполняет следующее. Если текущим элементом в списке категорий является элемент <Все> (cbCategories-Itemlndex = О), тогда выбирается индекс taAlbumindex3 {сортировка по дате). Метод CancelRange означает, что просматривается весь существующий диапазон строк. Если же выбран какой-либо другой элемент списка категорий, тогда выбирается индекс taAlbumindexS (сортировка по категориям) и устанавливается диапазон просмотра только тех строк таблицы, которые соответствуют текущей категории. Метод SetRange используется для определения диапазона просмотра записей по всем полям, входящим в индекс. Например, в данном случае по индексу taAlbumlndex3 при помощи метода SetRange можно было бы выбрать только
136
Глава 3. Работа с локальными базами данных
те фотографии, которые попадают в выбранную категорию и были сделаны в течение января текущего года: SetRangef[cbCategories.Text, EncodeDate(2003,1,1)], [cbCategorieB.Text, E n c o d e D a t e ( 2 0 0 3 , 1 , 3 1 ) ] ) ; 4. Теперь необходимо расставить в исходном коде модуля вызовы метода Setcontent. Этот метод должен вызываться в обработчиках событий mmiOpenAlbum.OnClick И cbCategories. OnChange. Первый ИЗ НИХ уже создан, поэтому просто добавьте в него вызов метода SetContent: Itemlndex := 0; end;
SetContent; paPhotos.Visible := True; 5. Создайте обработчик события cbCategories.OnChange и вставьте в него две строки программного кода: SetContent; dgrPhotos.SetFocus; Обработчик события TDataSource.OnDataChange Для компонента DataSource определены три события: OnDataChange, OnStateChange И OnUpdateData. • Событие OnDataChange возникает в момент изменения данных текущей строки вследствие редактирования одного из полей или перехода на другую запись. • Событие OnStateChange возникает в тот момент, когда изменяется состояние набора данных, связанного с компонентом DataSource (определяется свойством DataSet. State). • Событие OnUpdateData возникает в момент перед самым сохранением в таблице изменений, внесенных в текущую запись. Эти события часто используются в приложениях баз данных. В частности, событие OnDataChange позволяет определить момент перехода на новую запись в наборе данных. Реализуем в приложении "Фотоальбом" обработчик события dsAlbum. OnDataChange для формирования строки состояния и определения доступности элементов управления таблицей taAlbum. 1. Создайте обработчик события dsAlbum.OnDataChange. Для этого можно просто дважды щелкнуть мышью на компоненте dsAlbum в конструкторе форм. 2. Вставьте в обработчик следующий программный код:
Приложение "Фотоальбом"
137
stPhotos-SimpleText := 'Фотография ' + IntToStr(taAlbum.RecNo) + ' из ' + IntToStr(taAlbum.RecordCount); mmiRenamePhoto.Enabled := taAlbum.RecordCount > 0; tlbtRenamePhoto.Enabled := mmiRenamePhoto.Enabled; mmiDelPhoto.Enabled := mmiRenamePhoto.Enabled; tlbtDelPhoto.Enabled := mmiRenamePhoto.Enabled; В этом фрагменте для формирования строки состояния используются два свойства компонента Table: RecHo и RecordCount. Первое из них содержит номер текущей записи в наборе данных, а второй — общее количество записей. ПРИМЕЧАНИЕ __ В некоторых базах данных SQL-серверов понятие "номер текущей записи" отсутствует, и поэтому при обращении к таблицам соответствующего формата свойство RecNo всегда будет иметь значение -1. Затем в методе dsAlbumDataChange устанавливаются значения свойства Enabled для команд Фотографии х Переименовать, Фотографии >• Удалить и соответствующих им кнопок панели инструментов. (Если текущий набор записей пуст, тогда нечего переименовывать или удалять.) 3. Сохраните проект, запустите приложение и откройте альбом Семейный. Теперь команды Переименовать и Удалить должны быть недоступны, так как альбом еще не содержит ни одной фотографии. 4. Закройте приложение и вернитесь в конструктор форм. Создание формы выбора и дополнения фотографий в альбом 1. Добавьте в проект еще одну форму при помощи команды File >• New >• Form. Эта форма будет открываться при выполнении команды Фотографии >• Добавить. 2. Выполните команду File >• Save и присвойте программному модулю новой формы имя PAPic.pas. 3. Назовите новую форму fmPicture и присвойте ее свойствам значения в соответствии с табл. 3.14. Таблица 3.14. Свойства формы fmPicture
Свойство
Значение
Caption
Выбор фотографий
FormStyle
fsStayOnTop
Height
540
Position
poScreenCenter
Width
530
Пояснение Это означает, что форма при открытии всегда будет находиться на переднем плане. Располагается по центру экрана.
138
Глава 3. Работа с локальными базами данных
Эта форма будет создаваться при инициализации приложения вместе с главной формой, а значит динамически создаваться и уничтожаться не будет. 4. Добавьте в раздел uses интерфейсной части модуля PAMain.pas ссылку на модуль PAPic.pas, а в разделе implementation модуля PAPic.pas создайте раздел uses со ссылкой на модуль PAMain.pas: implementation uses PAMain; 5. Перейдите в конструкторе форм к главной форме приложения, откройте редактор компонента mmMain и дважды щелкните мышью на команде Фотографии >- Добавить. 6. Вставьте в обработчик события mmiNewPhoto.OnClick следующую строку: fmPicture.Show; 7. Сохраните проект, запустите приложение и откройте фотоальбом Семейный. 8. Выполните команду Фотографии >• Добавить или щелкните мышью на соответствующей этой команде кнопке панели инструментов. В результате откроется форма Выбор фотографий. Обратите внимание, что в отличие от форм, открываемых при помощи метода ShowModal, из этой формы можно переходить в главную форму приложения и обратно. 9. Закройте приложение и вернитесь в конструктор форм. Разработка формы fmPicture Перейдите к форме fmPicture и расположите на ней компоненты в соответствии с табл. 3.15. Таблица 3.15. Компоненты, размещенные иа форме fmPicture Свойство
Значение
Пояснение
Компонент Panel с вкладки Standard Align
alLeft
BevelOuter
bvLowered
Caption Name Width
paLeft
205
На панели paLef t расположите три компонента с вкладки Win 3.1 : • DriveComboBox; • ' DirectoryListBox; • FileListBox. Свойства компонента FileListBox: Anchors
[akLeft, akTop, akBottom]
Приложение "Фотоальбом"
139
Продолжение таблицы 3.15 Свойство
Значение
Extended-
False
Пояснение
Select
Height
323
Left
4
Mask
* .bmp
Name
flbPicture
Top
186
Width
196
В фотоальбом будут добавляться только изображения в формате Windows Bitmap (.bmp), так как этот формат поддерживается по умолчанию всеми графическими компонентами Delphi.
Свойства компонента DirectoryListBox PileList
flbPicture
Height
161
Left
4
Name
drlbPicture
Top
24
Width
197
Связываем компонент выбора каталога с компонентом выбора файла.
Свойства компонента DriveComboBox DirList
drlbPicture
Left
4
Maine
drcbPicture
Top
4
Width
197
Связываем компонент выбора диска с компонентом выбора каталога.
Следующие компоненты расположите на свободном пространстве формы frnPicture. Компоненты BitBtn с вкладки Additional: Компонент bbftdd Caption
Добавить
Enabled
False
Left
216
Top
8
Width
200
140
Глава 3. Работа с локальными базами данных
Продолжение таблицы 3.15 Свойство
Значение
Пояснение
Компонент Ъьс lose Kind
bkClose
Caption
Закрыть
Left
424
Top
8
Width
90
Компоненты Label с вкладки Standard: Компонент laCategory Caption
Категория:
Left
216
Top
44
Компонент laDate Caption
Дата:
Left
424
Top
44
Компонент laTitle Caption
Подпись :
Left
216
Top
88
"
Компонент ComboBox с вкладки Standard Left
216
Name
cbCategory
При активизации формы текста в поле Категория быть не должно-
Text
Top
60
Width
200
Компонент DateTimePicker с вкладки Win32 — используется для ввода даты или времени с возможностью выбора из календаря. Left
424
Name
dtpDate
Top
60
Width
90
Компонент Memo с вкладки Standard — используется для ввода многострочного текста.
Приложение "Фотоальбом"
141
Окончание таблицы 3.15 Свойство
Значение
Anchors
[akLeft, akTop, akRight]
Height
60
Left
216
Lines
MaxLength
100
Name
meTitle
Top
104
Width
300
Пояснение
.
Очистить в редакторе свойства Lines. Ограничение на допустимое количество мых символов.
ВВОДИ-
Компонент ScrollBox с вкладки Additional — панель с полосами прокрутки, которые отображаются при выходе компонентов за пределы видимости. Anchors
[akLeft, akTop, akRight, akBottom]
Height
340
Left
216
Name
sxPicture
Top
170
Width
300
Последним расположите на панели sxPicture компонент image с в кладки Additional. Размеры компонента будут автоматически измеAutoSize True няться в соответствии с размерами графического i изображения. Left
0
Name
imPicture
Top
0
Visible
False
При первом открытии формы fmPicture изображение видимо только в том случае, если выбран какой-либо файл .bmp в списке flbPicture.
Исходный код модуля формы fmPicture Исходный код программного модуля PAPic.pas весьма прост.
142
Глава 3. Работа с локальными базами данных
1. Для начала дважды щелкните мышью в каком-нибудь месте формы fmPicture, не занятой никакими компонентами. В результате будет создан обработчик события fmPicture.OnCreate, который вызывается при создании формы. 2. Вставьте в него следующую строку: dtpDate.Date := Now; Теперь при создании формы компонент dtpDate будет содержать-текущую дату (возвращается функцией Now). НЕМНОГО ТЕОРИИ Для работы с датами и временем в Delphi используется тип TDateTinie, к которому относится и свойство Date. По существу, этому типу соответствуют числа с плавающей запятой, а не строки. Целая часть числа соответствует дате, а дробная — времени. Отсчет дат ведется от 30 декабря 1899 года. Таким образом, если свойству Date присвоить значение 0, то оно будет соответствовать дате 30.12.139 9. Для работы с датами до 30 декабря 1899 года используются отрицательные значения величин тира TDateTirne. Так дате 29.12.1899 соответствует число -1. Учитывая тот факт, что даты — это, по своей сути, числа, к ним можно применять арифметические операции сложения и вычитания. Например, в результате выполнения операции How - 7 будет получена дата недельной давности. 3. Перейдите в инспекторе объектов на вкладку Events и создайте для формы fmPicture обработчик события OnActivate. 4. Вставьте в него две строки программного кода: cbCategory.Items.Assign(fmMain.cbCategories.Items); cbCategory.Items.Delete(0) ;
Это означает, что при активизации формы в список cbCategory переносятся все элементы списка fmMain. Categories, а затем из списка fmPicture.cbCategory удаляется первый элемент <Все>. 5. Создайте обработчик события flbPicture.OnChange и вставьте в него следующий фрагмент программного кода: imPicture.Visible := flbPicture.Items.Count > 0; if imPicture.Visible then begin if flbPicture.Itemlndex = -1 then flbPicture.Itemlndex := 0; imPicture.Picture.LoadFromFile(flbPicture.FileName); end; bbAdd.Enabled := imPicture.Visible and fmMain.paPhotos.Visible;
Компонент imPicture отображается только в том случае, если в выбранном каталоге есть хотя бы один файл .bmp. Если это условие выполняется, тогда те-
Приложение "Фотоальбом"
143
кущий файл, выбранный в списке, загружается при помощи метода LoadFromFile. В конце обработчика события выполняется проверка доступности кнопки Добавить. Эта кнопка будет доступна только в том случае, если в списке выбран какой-либо файл изображения, а также, если в главной форме в данный момент открыт какой-либо фотоальбом. 6. Последний метод, который необходимо создать в модуле формы frnPicture, — это обработчик события Onclick кнопки Добавить. Дважды щелкните мышью на кнопке bbAdd и вставьте в метод bbAddClick следующие строки: fmMain.taAlbum.InsertRecord([nil,cbCategory.Text, dtpDate.Date, meTitie.Text,imPicture.Picture]];
if fmMain.cbCategories.Items.IndexOf(cbCategory.Text) = -1 then fmMain.cbCategories.Items.Add(cbCategory.Text); В первом операторе при помощи метода InsertRecord в таблицу taAlbum вставляется новая запись. В качестве параметра метода InsertRecord используется массив значений, соответствующих порядку следования полей в таблице. • Так, первое поле ID, имеет тип Autolnc и вручную не редактируется, поэтому в него заносится значение nil, то есть, ничего не заносится. • В поле CatName заносится текущее значение, указанное в текстовом поле раскрывающегося списка cbCategory. • В поле PhDate заносится текущее значение даты, указанное в компоненте dtpDate. • В поле Title заносится текст, введенный в компоненте melitle. • В BLOB-поле Picture заносится значение свойства imPicture.Picture. Во втором операторе проверяется наличие введенной пользователем категории в списке категорий. Если такой категории в списке fmMain.cbCategories нет (indexOf (cbCategory. text) = -l), тогда в не.го добавляется новый элемент. 7. Сохраните проект и запустите приложение, 8. Откройте альбом Семейный и щелкните мышью в панели инструментов на кнопке Добавить. 9. В окне Выбор фотографий найдите какой-нибудь каталог с файлами .bmp и добавьте несколько фотографий в таблицу Семейный.db, не забыв указать для них категорию, дату и подпись. Для примера, в альбом семейный были добавлены две фотографии. • Файл baby.bmp в категорию Наш малыш с подписью "Мне уже год!" (рис. 3.21). • Файл housed .bmp в категорию Наш дом с подписью "Новоселье" (рис. 3.22). 10. Закройте форму Выбор фотографий, и поработайте немного с фотоальбомом. Для нашего ™т'мр,оа в списке категорий должно появится два новых элемента: "Наш лом" и "Наш ребенок" (рис, 3.23),
Рис. 3.21. Первая фотография, добавленная в альбом Семейный
Рис. 3.22. Вторая фотография, добавленная в альбом Семейный
Приложение "Фотоальбом"
145
11. Закройте приложение и вернитесь в конструктор форм. Теперь осталось только создать методы для переименования и удаления записей в таблице taAlbum. i / Фотоальбом
Рис. 3.23. Просмотр альбома Семейный по категории Наш дом Команды Фотографии >- Переименовать и Фотографии >• Удалить 1. Дважды щелкните мышью сначала на компоненте mmMain, а затем -- в редакторе меню на команде Фотографии >- Переименовать. 2. Вставьте в метод mmiRenamePhotoClick следующие строки: procedure TfraMain,mmiRenamePhotoClick(Sender: TObject); var s:string; begin s := taAlbumTitle.AsString; if InputQuery('Подпись','Измените:',a) then begin taAlbum.Edit; taAlbumTitle.AsString := s; taAlbum.Post; end; end;
Вначале в строковой переменной s сохраняется текущее значение поля Title, а затем на экране отображается запрос на изменение этого значения. Если пользователь ответит утвердительно, текущая запись таблицы taAlbum будет переведена в состояние редактирования, а в поле Title будет занесено новое
146
Глава 3. Работа с локальными базами данных
значение. Затем внесенное в запись изменение сохраняется при помощи метода Post. Редактирование записей локальных таблиц всегда выполняется в представленной выше последовательности: вызов метода Edit, присвоение новых значений, вызов метода Post. Если попытаться занести какое-либо значение в поле, не переведя запись в состояние редактирования, будет выдано сообщение об ошибке. Также, если не вызвать метод Post, то при закрытии таблицы изменения, внесенные в текущую запись, будут утеряны. 3. Теперь создайте обработчик события mmiDelPhoto.OnClick и вставьте в него следующий программный код: if M e s s a g e D l g f ' У д а л и т ь фотографию '+ #13 + ' " ' + taAlbuinTitle.flsString + ' " ? ' , mtConfirmation,[mbYes,rabNo],0) = mrYes
then taAlbum.Delete;
Понять работу этого обработчика события нетрудно: после утвердительного ответа пользователя на запрос вызывается метод taAlbum. Delete, удаляющий текущую запись таблицы. 4. Вот и все! Теперь можно в последний раз сохранить проект и откомпилировать приложение с минимальными настройками компилятора. Е) Полный листинг исходного кола приложения "Фотоальбом" находится на прилагаемой к книге дискете.
Что дальше? Эта глава была посвящена локальным базам данных. Далее будут рассмотрены методы работы с SQL-серверами. Но прежде, чем перейти к этой объемной теме, необходимо хотя бы поверхностно ознакомиться с языком SQL, который является базовым для всех SQL-серверов. Именно этому вопросу и посвящена следующая глава.
,
Глава 4
Основы языка SQL Данная глава написана, исходя из следующих предположений, что читатель не обладает какими-либо знаниями по SQL, и, что в его распоряжении имеется какой-нибудь сервер базы данных, поддерживающий SQL, а также средство для передачи запросов этому серверу. Базовой для языка SQL является спецификация ANSI. Команды этой спецификации могут выполняться абсолютно для всех баз данных, поддерживающих SQL (это относится и к локальным базам данных, наподобие Paradox). Для выполнения SQL-команд при работе с локальными базами данных можно использовать специализированные средства, подобные программам SQL Explorer или Database Desktop, поставляемых вместе с Delphi. Следует отметить, что примеры, рассматриваемые в этой главе, ориентированы на сервер InterBase, однако почти все представленные SQL-команды применимы и к локальным таблицами. Те SQL-команды, которые при работе с локальными базами данных не используются, будут выделены при помощи примечаний. Как уже указывалось в предыдущей главе, каждый сервер баз данных расширяет и дополняет спецификацию ANSI, формируя таким образом собственный "диалект" языка SQL. В этой и последующих главах будет рассмотрена работа с сервером баз данных InterBase 6.5. Особое внимание будет уделено особенностям диалекта- языка SQL этого сервера. Если для работы будет использован какой-нибудь другой SQL-сервер, то необходимо отметить следующее: такие серверы как Sybase и Microsoft SQL Server используют синтаксис SQL, который значительно отличается от стандарта ANSI, в то время как большинство других SQL-серверов, включая InterBase и Oracle, дополнили стандартные операторы ANSI лишь в незначительной степени. Таким образом, примеры, рассматриваемые в данной главе, могут некорректно выполняться на серверах Sybase и Microsoft SQL Server. Для выполнения команд SQL, представленных в этой главе, можно воспользоваться уже знакомой программой SQL Explorer. Программа подробно рассмотрена в разделе "Программа SQL Explorer" главы 2. Однако, раз уж здесь рассматривается работа SQL-сервера InterBase, лучше все-таки освоить средства, поставляемые вместе с этим сервером.
Установка и запуск сервера InterBase 6.5 Сервер InterBase поставляется вместе с выпуском Delphi 7 Enterprise и устанавливается отдельно. Процесс установки этого сервера не сложен — достаточно просто подтверждать параметры, выбранные мастером установки. Единст-
148
Глава 4. Основы языка SOL
венный выбор мастера, который можно изменить,— это тип установки. Для изучения примеров, представленных в данной книге, можно выбрать в окне мастера Select Components (Выбор компонентов) сокращенную установку (Compact Installation). После завершения установки сервера InterBase 6.5 способ его запуска определяется при помощи специальной программы Interbase Manager. Сервер может запускаться либо автоматически при загрузке операционной системы (переключатель Automatic), либо вручную (переключатель Manual). В случае использования операционных систем на базе технологии NT (например, Windows 2000), с автоматическим запуском сервера InterBase могут возникнуть проблемы. В этом случае можно запускать его вручную, щелкнув в программе Interbase Manager на кнопке Start, предварительно сбросив флажок Run the InterBase server as a service on Windows NT (Запускать сервер InterBase как службу Windows NT).
Приложение IBConsole Все средства администрирования и работы с базами данных InterBase 6.5 (включая редактор SQL-запросов) находятся в одном приложении — IBConsole. Прежде, чем приступить к работе с этим приложением, убедитесь, что сервер InterBase уже запущен. Запустите приложение IBConsole при помощи меню Пуск или любым другим доступным способом. Внешний вид этого приложения при его первом запуске представлен на рис. 4.1. Сига*; - View serve-
Dststa» Tools windows Hub
M*»i Рвта&ш; UrtAcoaigcii Eomaare
.Jj 1п1вВа«Звув)
'
,
•
•
"
1
i
,«!>•
"
1
:
Л
A
Рис. 4.1. Приложение IBConsole
Подключение к серверу Как видно на рис. 4.1, в левой части окна IBConsole расположен список зарегистрированных серверов InterBase. •
Приложение IBConsole В данный момент этот список пуст, потому что еще ни один из серверов не зарегистрирован. 1. Выполните команду Server >- Register или просто дважды щелкните мышью на корневом элементе древовидной структуры InterBase Servers, чтобы зарегистрировать сервер. В результате на экране появится диалоговое окно регистрации сервера и его подключения (рис. 4.2).
149
i Peg i si ei Spiver and Correct
Ntfwak Piotocc*
Здесь следует указать, к какому серверу будет User Name выполняться подключение: к локальному — переключатель Local Server, или к удаленному — EJK. переключатель Remote Server. Локальным является сервер, установленный на компьютеРис. 4.2. Диалоговое окно ре пользователя, а удаленным — сервер, досRegister Server and Connect туп к которому выполняется через сеть. В случае удаленного подключения в поле Server Name указывается сетевое имя сервера или сетевой адрес, соответствующий протоколу, указанному в поле Network Protocol. 2. Щелкните мышью на переключателе Local Server. В разделе Login Information указывается имя пользователя и пароль, без которых нельзя подключиться ни к одной базе данных. В InterBase в качестве имени стандартного пользователя с полными правами администратора указывают SYSDBA, а в качестве пароля -- masterkey. При желании эти значения в дальнейшем можно изменить. 3. Введите в поле User Name слово SYSDBA, а в поле Password слово masterkey. Обратите внимание на то, что вместо вводимых символов пароля отображаются звездочки. Если имя пользователя и пароль были введены правильно, то произойдет подключение к локальному серверу InterBase, и у корневого элемента InterBase Servers появится дочерний элемент Local Server. 4. Дважды щелкните на нем мышью, чтобы раскрыть список подчиненных элементов (рис. 4.3). -
Как видно на рис. 4.3, с локальным сервером связано несколько элементов. Для нас на данном этапе интерес представляют только два из них: Databases и Users. Если щелкнуть мышью на элементе Users, то в правой части окна IBConsole отобразится список всех пользователей, зарегистрированных для данного сервера. Для того чтобы изменить набор пользователей или пароли для некоторых из них, можно выполнить команду Server >- User Security. »>
Вопросы администрирования баз данных InterBase рассматриваются в главе 8.
1 50
Глава 4. Основы языка SQL
Gftscte
View
Server
Database
Toob
P1 ^ IrtfaiSateSeivers
Windows
Hdp
AcQcfi L c-goLK
i 1
;
D«pption Logoul iiom Ihe cmrenl server
^ Dalabasei
Uuf Secuiy
Manage InterBase Users
ИВ Backup
Propefbes
View ^erveiprcpeUe!
Щ Cwrtcates
Add Cert'cale
Add a icons* ceinficale
_£J Server Log
RHDOYF CerUicale Remove a bcense certrfeale
l lser9
(p -
SnW:L^Senr
f
^
^
M»^SYSDBA
/A
Рис. 4.З. Выполнено подключение к локальному серверу
Регистрация существующей базы данных При помощи элемента Databases можно либо зарегистрировать на сервере уже существующую базу данных InterBase, либо создать новую. Создание базы данных рассматривается чуть позже. Зарегистрируем базу данных. 1. Выполните команду Database >- Register и щелкните мышью на кнопке с тремя точками, расположенной справа от поля Database. 2. Файлы баз данных InterBase имеют расширение .gdb. Выберите в качестве регистрируемой базы данных файл employee.gdb, который по умолчанию расположен в каталоге \ProgramFiles\Common Files\Borland Shared\Data. 3. Затем щелкните мышью на кнопке ОК. В результате у элемента Databases в древовидной структуре появится подчиненный элемент EMPLOYEE.GDB, который в свою очередь имеет собственный набор подчиненных элементов. К элементам базы данных относятся домены (Domains), таблицы (Tables), индексы (Indexes), представления (Views), хранимые процедуры (Stored Procedures), внешние функции (External Functions), генераторы (Generators), исключения (Exceptions), BLOB-фильтры (BLOB Filters) и роли (Roles). Из перечисленных элементов в предыдущей главе уже рассматривались таблицы и индексы, а остальные из них будут рассмотрены по мере необходимости для изучения материала книги.
Создание базы данных Базу данных можно создать при помощи специальной команды языка SQL (И- этот способ рассматривается в разделе "Создание базы данных" этой главы), но это можно также сделать и средствами приложения IBConsole.
Приложение IBConsole
151
1. Щелкните правой кнопкой мыши на элементе Databases и выполните команду Create Database раскрывшегося контекстного меню. В результате на экране появится диалоговое окно создания базы данных (рис. 4.4).
Рис. 4.4. Диалоговое окно Create Database 2. В этом окне можно указать расположение и название файла базы данных, и его размер в единицах, называемых страницами. Размер одной страницы указывается ниже в поле Page Size. По умолчанию он равен 4096 байт (4 Кбайта). Таким образом, для того чтобы определить общий размер базы данных в Кбайтах, необходимо умножить количество страниц на размер одной страницы. 3. В поле Default Character Set указывается тип кодировки символов строковых полей базы данных. Можно оставить в этом поле значение None. 4. Значение, указанное в поле SQL Dialect, определяет характер интерпретации двойных кавычек, больших чисел и некоторых типов данных. Для большинства случаев необходимо выбрать диалект 3. 5. В поле Alias можно указать псевдоним, под которым база данных будет зарегистрирована на сервере. Если необходимо пропустить регистрацию создаваемой базы данных, сбросьте флажок Register Database - - в этом случае база данных будет создана физически, но не отобразится в программе IBConsole. 6- После того, как все параметры базы данных указаны, можно щелкнуть мышью на кнопке ОК. В результате будет создана база данных, размещение и имя которой было указано в поле File.
152
Глава 4. Основы языка SQL
Команды SQL
•
Приступим к изучению языка SQL. Выполните в программе IBConsole команду Tools >- Interactive SQL. В результате откроется средство обработки SQL-запросов Interactive SQL, с которым мы и будем работать в дальнейшем (рис. 4.5). lxl
*1 Interatlt™ SQL - EMPLOYS.!!!» Fie еЛ Query ОааЬи* Trweectons Windows
Help
ф? - ?ф ' Ф> В" % ' ""' "^ ' ff ' i- И -Sj * ^
~3
Llient Select I
I^Jo active FransacUcn,
Рис. 4.5. Средство Interactive SQL Все команды, которые будут рассмотрены в этом разделе, будут выполняться в окне Interactive SQL. Для того чтобы выполнить SQL-запрос сделайте следующее. 1. Введите текст SQL-запроса в верхнее поле окна Interactive SQL. 2. Затем выполните команду Query >- Execute () или щелкните мышью в панели инструментов на кнопке с изображением молнии. Результаты выполнения команды SELECT будут отображены в нижней части окна.
Создание базы данных Примеры этой главы представляют собой подготовку к разработке приложения "Персонал", которое рассматривается в главе 6. По этой причине рекомендуем создать отдельный каталог, в котором можно сохранить файл базы данных, а в дальнейшем — файлы проекта Delphi. Пусть база данных приложения "Персонал" будет называться STAFF.GDB, и будет размещена в каталоге . . \ s t a f f \ b a s e (в рассматриваемых ниже примерах используется каталог е: \programs\staff). 1. Прежде, чем приступить к выполнению команд SQL, предназначенных для создания базы данных и ее элементов, выполните в окне Interactive SQL следующую команду:
Команды SQL
1 53
SET SQL DIALECT 3 ПРИМЕЧАНИЕ Если при выполнении команды SET SQL DIALECT выводится сообщение об ошибке, это свидетельствует о том, что в данный момент активна какая-то база данных. В этом случае необходимо выполнить команду Database >- Disconnect. Для создания баз данных в SQL используется команда CREATE DATABASE. Создадим базу данных Staff .gdb. 2. Для этого введите и выполните в окне Interactive SQL следующий SQLоператор: CREATE DATABASE " e : \ p r o g r a m s \ s t a f f \ b a s e \ s t a f f . g d b " USER "SYSDBA" PASSWORD "rnasterkey" PAGE_SIZE = 4096 LENGTH = 10000 PAGES
В результате в каталоге e : \ p r o g r a m s \ s t a f f \ b a s e будет создан файл базы данных s t a f f .gdb. Размер этой базы данных не может превышать 4 Кбайт * 10000 = 40 Мбайт. ПРИМЕЧАНИЕ Команды SET SQL DIA.LECT и CREATE DATABASE при работе с локальными базами данных не используются. В отличие от локальных баз данных, состоящих из набора разнообразных файлов, все объекты серверных баз данных находятся в одном файле (в случае с InterBase — в файле . gdb). ПРИМЕЧАНИЕ После выполнения команды CREATE DATABASE в окне Interactive SQL созданная база данных становится активной. Это означает, что все дальнейшие SQL-команды будут применяться именно к этой базе данных до тех пор, пока не будет выполнена команда DISCONNECT. Для повторного подключения к базе данных используется команда SQL CONNECT.
Создание таблиц Прежде, чем приступить к созданию таблиц, имеет смысл рассмотреть, какие типы данных используются в InterBase. Все они перечислены в табл. 4.1. Таблица 4.1. Типы данных InterBase Тип данных BLOB
Диапазон значений
Описание Для хранения больших данных, подобных графике, тексту и цифровому звуку.
154
Глава 4. Основы языка SQL
Окончание таблицы 4.1 Тип данных
Диапазон значений
Описание
CHAR(n)
От 1 до 32765 символов
Строковый тип фиксированной длины (п символов).
DATE
От 01.01.01 00 до 11.01.5941
Дата и время.
DECIMAL (p,S)
До 15 разрядов
Числа с десятичной точкой. Параметру р соответствует общее количество разрядов, а параметру s — количество цифр после запятой. Так, поле типа DECIMAL^, 2) может содержать числа только в формате
•
ррр, ss. DOUBLE
PRE-
От1,7х10-
308
Пятнадцатиразрядные числа с плавающей запятой.
CISION
Д01.7Х10 308
FLOAT
ОтЗ,4хЮ' 3 '
Семиразрядные числа с плавающей запятой.
INTEGER
до 3,4x10 От -2147483648 до 2147483647
Длинные целые числа со знаком.
NUMERIC (p,s)
До 15 разрядов
Аналогичны числам DECIMAL, но не обязательно должны состоять из того количества разрядов, которое задано параметром р. Другими словами поле типа NUMBER (5, 2) может содержать число 12 , 2.
SMALLINT
От -32768 до 32767
Короткие целые числа со знаком.
VARCHAR(n)
От 1 до 32765 символов
Строковый тип переменной длины (до п символов).
ор
ПРИМЕЧАНИЕ При создании локальных таблиц типы данных DECIMAL, DOUBLE PRECISION и VARCHAR не используются. Для создания таблиц используется оператор SQL CREATE TABLE. Приложение "Персонал" достаточно упрощено и, конечно же, не удовлетворит всех нужд отделов кадров. В этой книге не создается обширная база данных, в которой можно было бы хранить всестороннюю информацию о сотрудниках предприятия. Это не является основной задачей книги, к тому же, только для описания процесса разработки такого крупномасштабного приложения может потребоваться отдельная книга. Создадим шесть таблиц.
Команды SQL
155
• STAFF — список сотрудников с некоторыми индивидуальными данными. • REGIONS — список районов в местности, в которой расположено предприятие, сформированный в соответствии с почтовыми индексами. • DEPS — список подразделений предприятия. • POSS — список должностей. • JOBS — таблица для хранения информации о трудовой деятельности сотрудников. • FAMILY — таблица для хранения информации о членах семьи сотрудников. Итак, приступим. оператор:
Выполните в окне Interactive SQL следующий SQL-
CREATE TABLE STAFF ( ID integer NOT HULL PRIMARY KEY, LastName varchar(30) NOT NULL, FirstName varchar(30) NOT NULL, FatherName v a r c h a r ( 3 0 ) , Zip integer,
Street varchar(30), House varchar(10), Tel varchar(20), TaxCode numeric(12,0) DEFAULT 0 NOT NULL, TabNum varchar(lO), BirthDate date NOT NULL, DepID smallint NOT NULL, PosID smallint NOT NULL, BornPlace varchar(50), Salary numeric(15,2) DEFAULT 200.0 NOT NULL, PasspNum varchar(20), PasspDate date, Photo blob ) Поясним назначение полей таблицы. • STAFF. ID — уникальный идентификатор записи, который используется для построения первичного ключа. • LastName, FirstName и FatherName — соответственно фамилия, имя и отчество. • Zip, street, House, Tel — соответственно почтовый индекс, город, улица, номер дома и квартиры, номер телефона. • TaxCode — идентификационный номер плательщика налогов. • TabNum — табельный номер по реестру предприятия. BirthDate — дата рождения. DepiD и POSID — идентификаторы подразделения и должности. BornPlace — место рождения. Salary — размер зарплаты.
156
Глава 4. Основы языка SQL
• PasspNum и PasspDate — номер и дата выдачи паспорта. • Photo — фотография. Атрибуту PRIMARY KEY соответствует первичный ключ. Обратите также внимание на то, что некоторые строки заканчиваются атрибутом NOT NULL. Это означает, что данное поле не может содержать пустых значений. В частности, те поля, которые попадают в первичный ключ, обязательно должны быть определены как NOT NULL. Кроме того, при определении поля можно использовать ключевое слово DEFAULT, после которого следует некоторое значение. Это значение будет вноситься в поле при добавлении в таблицу новой строки. В частности, в поле Salary при создании новой строки будет заноситься значение 200. ПРИМЕЧАНИЕ При создании локальных таблиц атрибуты PRIMARY KEY и НОТ NULL не используются. Для создания первичных ключей используются другие механизмы, описанные в следующей главе, в разделе "Первичный ключ". CREATE TABLE REGIONS ( Zip integer NOT HULL PRIMARY KEY, Area varchar(30), Region varchar(30), City varchar(20) NOT NULL ) В строках этой таблицы содержится следующая информация: почтовый индекс, название региона, название района и название населенного пункта. CREATE TABLE DEPS ( DeptID integer HOT NULL PRIMARY KEY, DeptFullName varchar(lOO) NOT NULL, DeptShortName varchar(10) NOT NULL, ParentDeptID integer DEFAULT 0 NOT NULL, Parents varchar(100|) В строках этой таблицы содержится следующая информация: идентификатор, а также полное и сокращенное название подразделения. Для корневых элементов в иерархической структуре подразделений поле ParentDeptID содержит значение 0, а для остальных — идентификатор "родительского" подразделения. В поле Parents содержится строка с последовательным перечнем идентификаторов всех "родительских" подразделений. Предположим, существует подразделение с идентификатором 1, и у него есть подчиненное подразделение с индикатором 3. В этом случае для подразделения, подчиненного подразделению с идентификатором 3, в поле Parents будет содержаться значение 1, 3. У подразделений, расположенных на вершине иерархии, поле Parents содержит значение NULL. CREATE TABLE POSS
•
[ PosID integer NOT NULL PRIMARY KEY,
Команды SQL
157
PosFullName varchar(lOO) NOT HULL, PosShortName varchar(20) NOT NULL, PosLevel smallint DEFAULT 1 NOT NULL] В строках этой таблицы содержится следующая информация: идентификатор, полное и сокращенное название должности, а также информация об уровне должности. Чем меньше число, тем выше уровень. CREATE TABLE JOBS ( EmpID integer NOT NULL, StartDate date NOT NULL, StopDate date. Organization varchar(lOO) NOT NULL, Dep varchar(100), Pos varchar(100) NOT NULL, StopCauses varchar(100), CurOrg char(1), InOrderNum v a r c h a r ( 1 0 ) , OutOrderNum v a r c h a r j l Q ) |
В строках этой таблицы содержится следующая информация: идентификатор сотрудника, дата начала и окончания трудовой деятельности, название организации, название подразделения, должность, причина увольнения/перевода, метка текущей организации и номера приказов о назначении и увольнении. CREATE TABLE FAMILY ( EmpID integer NOT NULL, kin varchar(S) NOT NULL, BirthDate date, KinNarae varchar(50) NOT NULL) В строках этой таблицы содержится следующая информация: идентификатор сотрудника, тип родственной .связи (жена, муж, сын, дочь и т.п.), дата рождения и имя родственника.
Удаление таблиц Оператор удаления таблицы очень прост: DROP TABLE имя таблицы Если таблица имеет связи с другими таблицами по внешним ключам (рассматриваются ниже), тогда команда DROP TABLE выполнена не будет.
Индексы Для создания индексов в SQL используется команда CREATE INDEX. При определении первичных и внешних ключей соответствующие индексы создаются автоматически, во всех остальных случаях их необходимо создавать самостоятельно.
158
Глава 4. Основы языка SQL
1. Создадим индекс для таблицы STAFF, в котором строки отсортированы в соответствии с фамилиями, именами и отчествами: CREATE INDEX STAFF_IHDEXl_j)N STAFF (LastName,FirstName,FatherHame)
2. Создадим для этой же таблицы еще два индекса: по столбцу с кодом плательщика налогов и по столбцу с табельным номером. CREATE UNIQUE INDEX STAFF_INDEX2 ON STAFF (TaxCode); CREATE UNIQUE INDEX STAFF_INDEX3 ON STAFF (TabNum)
Ключевое слово UNIQUE означает, что данные индексы не допускают повторений. 3. Теперь создадим индекс для таблицы JOBS: CREATE INDEX JOBS_INDEX1 ON JOBS (EmpID, StartDate)
Этот индекс будет использоваться для сортировки строк по дате начала трудовой деятельности. -
Добавление данных Для добавления строки данных в таблицу используется оператор INSERT. Например, добавим пару строк в таблицу REGIONS базы данных s t a f f . gdb. INSERT INTO REGIONS VALUES(8320,'Киевская','','Борисполь'); INSERT INTO REGIONS VALUES(8324, 'Киевская', 'Бориспольский', Тора' |
ПРИМЕЧАНИЕ Обратите внимание на символ ";" после первого оператора INSERT, Точка с запятой является в InterBase установленным по умолчанию разделителем операторов SQL Разделители используются в тех случаях, когда необходимо сразу выполнить несколько операторов SQL (например, при помощи окна Interactive SQL). Тип разделителя можно установить при помощи команды SET TERM.
Рассмотрим, что выполняют приведенные выше операторы SQL. В первом операторе INSERT в таблицу REGIONS добавляется строка, соответствующая городу Борисполь, расположенному в Киевской области, а во втором — строка, соответствующая селу Гора, расположенному в Бориспольском районе. В этом операторе INSERT данные вносились во все поля. Теперь рассмотрим вариант оператора INSERT, который используется для выборочной вставки данных. Добавим одну строку в таблицу STAFF (обратите внимание на формат даты: мм/дд/гг): INSERT INTO STAFF(ID,LastName,FirstName,FatherName, Zip,BitthDate,DepID,PosID,TabNum) VALUES(0, 'Шпак', 'Юрий', 'Алексеевич1,8324; '08/07/1974',О,О, 'О' )
В результате в таблицу STAFF была добавлена запись с идентификатором 0.
Команды SQL
159
Команды COMMIT и ROLLBACK Группа изменений, вносимых в базу данных (например, при помощи команды INSERT), называется транзакцией (transaction). Понятие транзакций используется только при работе с SQL-серверами, а в работе с локальными базами данных не применяется. Все изменения, сделанные в пределах одной транзакции, можно внести в базу данных при помощи команды COMMIT. Пока эта команда не выполнена, изменения в базе данных не сохраняются. Команда ROLLBACK наоборот отменяет все изменения, внесенные в пределах одной транзакции. Действие обеих команд распространяется только на те транзакции, которые имели место быть после последней команды COMMIT. Другими словами, те изменения, которые уже были сохранены в базе данных, отменить уже нельзя. >» См. об удалении в разделе "Удаление строк".
В окне Interactive SQL команды COMMIT и ROLLBACK можно также выполнить, выбрав соответствующую им команду из меню Transactions. ПРИМЕЧАНИЕ При работе с локальными базами данных транзакции, а следовательно и команды COMMIT и ROLLBACK, не используются. _
Просмотр данных Для просмотра данных в таблицах используется команда SELECT. В простейшей форме эта команда имеет следующий синтаксис: SELECT * FROM REGIONS
1. Выполните эту команду в окне Interactive SQL, и в нижней части окна отобразится набор строк, выбранных из таблицы REGIONS. Символ "*" означает, что из таблицы REGIONS должны быть извлечены данные обо всех столбцах. 2. Несколько видоизмените эту команду: SELECT Zip, City FROM REGIONS В результате будут выбраны данные только из столбцов zip и City. 3. Выполните следующую команду: 5ELECT_ Area jTROM REGIONS
В результате будет выбрано две строки, которые обе содержат значение Киевская. 4. Несколько модифицируйте предыдущую команду SELECT: SELECT DISTINCT Area From REGIONS После выполнения этой команды будет выбрана только одна строка. Ключевое слово DISTINCT используется для выбора строк, в которых значения в указанных полях не повторяются.
160
Глава 4. Основы языка SQL
Выражения в столбцах В качестве столбцов в операторе SELECT можно указывать не только имена столбцов таблицы. Вполне допустимы также выражения, содержащие, кроме имени столбца, математические операции, абсолютные значения и вызовы функций. Чаще всего используются следующие функции: COUNT, SUM, AVG, MAX и MIN. Функция COUNT выполняет подсчет строк. Например, в результате выполнения следующего оператора SELECT COUNT (*) FROM REGIONS будет возвращено число 2 — количество строк в таблице REGIONS. Функция зим суммирует значения указанного в ней поля для всех выбранных строк. Так, в результате выполнения оператора SELECT SUM ( Z i p ) FROM REGIONS будет возвращено число 16644 (8324 + 8320). Функция AVG определяет среднее арифметическое значений полей всех выбранных строк. Например, в результате выполнения оператора SELECT AVG (Zip) FROM REGIONS будет возвращено число 8322 ((8324 + 8320) / 2). Функция МАХ определяет наибольшее значение поля среди выбранных строк, а функция MIN — наименьшее. Так, в результате выполнения оператора SELECT MAX ( z i p ) , MIN (zip) FROM REGIONS будет возвращена строка, содержащая два числа: 8324 и 8320.
Ключевое слово WHERE Ключевое слово WHERE применяется в SQL для наложения условий на значения, выбираемые при помощи оператора SELECT. Выполните следующий оператор: SELECT * FROM REGIONS WHERE Zip = 8324
В результате будет выбрана одна строка, у которой в столбце Zip указано значение 8324. В представленном ниже операторе осуществляется подсчет количества строк таблицы REGIONS, у которых значения столбца REGION начинается с "Борисп": SELECT COUNT(*) FROM REGIONS WHERE Region LIKE 'Еорисп%'
В результате будет возвращено число 1. Рассмотрим еще несколько вариантов оператора SELECT с ключевым словом WHERE.
SELECT C O U N T ( * ) FROM REGIONS WHERE Zip BETWEEN 83M^MD_8320
Здесь выполняется подсчет строк, в которых значение поля Zip лежит в диапазоне от 8300 до 8320. SELECT * FROM REGIONS WHERE Zip IN (8300,8310,8320)
Команды SOL
161
Здесь выбираются строки, в которых значение поля zip является одним из значений, указанных после ключевого слова INSELECT M A X [ Z i p ] FROM REGIONS
WHERE Region IS NOT NULL
•
В этом операторе определяется наибольшее значение поля Zip среди строк таблицы REGIONS, у которых в поле Region занесено какое-то значение. SELECT * FROM REGIONS WHERE Region LIKE 'Борисп%' OR City = 'Борисполь' Здесь выбираются строки, в которых значение поля Region начинается с "Борисп" или поле City содержит значение "Борисполь".
Объединения Еще одной областью применения ключевого слова WHERE является объединение таблиц для поиска информации в логически едином наборе данных. Операция объединения выполняется в два этапа: сначала с помощью ключевого слова FROM в операторе SELECT перечисляются объединяемые таблицы, а затем происходит объединение этих таблиц по нужному полю с помощью ключевого слова WHERE. Рассмотрим следующий пример: SELECT STAFF.LastName, STAFF.FirstName, STAFF.FatherName, REGIONS.City FROM STAFF, REGIONS WHERE STAFF.Zip = REGIONS.ZIP В результате выполнения этого оператора будет возвращена строка, состоящая из четырех полей: Шпак, Юрий, Алексеевич, Гора. Здесь было выполнено объединение таблицы STAFF с таблицей REGION по столбцу Zip. Таблица, указанная в условии WHERE слева от знака равенства, называется внешней (outer), a таблица, указанная справа — внутренней (inner). НЕМНОГО ТЕОРИИ Объединения, подобные объединению представленному выше, называются внутренними (inner join). Такие объединения возвращают только те строки, для которых выполняется условие объединения. Объединения могут быть также и внешними (outer join), то есть такими, которые возвращают строки вне зависимости от выполнения условия объединения. При использовании внешних объединений все поля внутренней таблицы, не соответствующие условию объединения, возвращаются со значением NULL. Синтаксис ANSI для создания внешних и внутренних объединений имеет следующий вид: SELECT Tablel.Columnl, Table2.Column2 FROM Tablel OUTER [INNER] JOIN Table2 ON Tablel.Columnl = Table2.ColumnS
'
162
Глава 4. Основы языка SQL
Вложенные запросы Вложенные запросы — это операторы SELECT, используемые в той части запроса, которая определяется ключевым словом WHERE. Такие запросы применяются для предварительного выбора информации, которая затем выбирается с помощью основного оператора SELECT. Рассмотрим пример: SELECT * FROM STAFF WHERE PosID IN (SELECT PosID FROM POSS WHERE PosLevel BETWEEN 1 AMD 3)
В этом операторе сначала выполняется вложенный запрос, в котором выбираются идентификаторы должностей уровня 1, 2 или 3. Затем выбираются все строки из таблицы STAFF, у которых значение поля PosID входит в набор ранее выбранных идентификаторов должностей.
Операция GROUP BY При помощи операции GROUP BY в SQL выполняется группирование данных по определенным полям. Обычно эта операция применяется в том случае, если в качестве последнего поля оператора SELECT используется математическая функция. Рассмотрим пример: SELECT DEPS.DeptFullName, SUM(STAFF.Salary) FROM DEPS, STAFF WHERE STAFF.DepID = DEPS.DeptID GROUP BY DEPS.DeptFullHame
В этом операторе выполняется подсчет суммы зарплаты сотрудников с группировкой по отделам. При этом используется объединение таблиц STAFF и DEPS, так как в таблице STAFF хранится только идентификатор подразделения. Рассмотрим еще один пример использования операции группирования: SELECT LastName, C O U N T f * ) FROM STAFF GROUP JJY LastHame
При помощи такого оператора выполняется подсчет количества однофамильцев в таблице STAFF.
Ключевое слово HAVING Ключевое слово HAVING используется для выбора строк, возвращаемых в результате выполнения запроса, в котором применяется ключевая операция GROUP BY. Соотношение ключевых слов HAVING и GROUP BY примерно такое же, как соотношение ключевого слова WHERE и оператора SELECT. Отличие состоит только в том, что HAVING работает со строками, возвращаемыми запросом, а WHERE — со строками исходной таблицы. Как правило, запрос, в котором используется слово HAVING можно переписать таким образом, чтобы обойтись без него. Дело в том, что запросы с HAVING
Команды SOL
163
в большинстве случаев менее эффективны по сравнению с теми запросами, в которых используется только ключевое слово WHERE. Рассмотрим пример запроса с использованием ключевого слова HAVING. SELECT LastName, COUNT(*) FROM STAFF GROUP BY LastName HAVING LastName LIKE 'A%' В этом операторе выполняется подсчет однофамильцев, у которых фамилия начинается с буквы "А". Этот же запрос можно записать в другом виде, исключив слово HAVING: SELECT LastName, COUNT{*) FROM STAFF WHERE LastName LIKE 'A%' GROUP_ BY LastHame
Операция ORDER BY Операция ORDER BY используется для сортировки возвращаемого набора данных. Например: SELECT * FROM STAFF ORDER BY LastHame, FirstName, FatherName В результате выполнения этого запроса будут возвращены все строки таблицы STAFF, отсортированные по фамилиям, именам и отчествам.
Псевдонимы столбцов Для представления результата математических функций можно использовать имена логических псевдостолбцов. Такие имена называются псевдонимами столбцов (column alias). Они предназначены для того, чтобы сделать текст запроса более понятным. В стандарте ANSI SQL псевдонимы столбцов размещаются в операторе SELECT справа от соответствующих полей с функциями. Например: SELECT LastName, COUHT(*) CountOfNames FROM STAFF GROUP BY LastName В результирующем наборе данных второе поле, содержащее количество фамилий, будет называться CountOfNames. ПРЕДУПРЕЖДЕНИЕ В списках, определяемых ключевыми словами WHERE или GROUP BY, псевдонимы не допускаются. Здесь всегда необходимо указывать реальные имена столбцов.
Псевдонимы таблиц Для того чтобы не указывать каждый раз полное имя таблицы в разных частях команды SELECT, можно создать его сокращенный вариант. Такое укоро-
164
Глава 4. Основы языка SQL
ченное имя называется псевдонимом таблицы (table alias). Оно создается при помощи ключевого слова FROM оператора SELECT: SELECT S.LastName, S.FirstNarae, FROM STAFF S, REGIONS R WHERE S.Zip = R . Z I P
S. FatherName, R . C i t y
Изменение данных Для изменения информации, хранящейся в таблицах базы данных, используется команда SQL UPDATE. Рассмотрим примеры использования этой команды. UPDATE STAFF SET Zip = 8320 В этом операторе во все строки таблицы STAFF в поле Zip вносится значение 8320. Еше один пример: UPDATE STAFF SET Salary = Salary + 100 WHERE PosID IN (SELECT PosID FROM POSS WHERE PosLevel'< 3) В этом операторе значение поля Salary увеличивается на 100 в тех строках, в которых должности сотрудника соответствует уровень не выше 3.. И последний пример: UPDATE STAFF SET TabNum = '00001', PosID = 2 WHERE ID = 0
В этом операторе в строке с данными сотрудника, имеющей идентификатор о, в поле TabNum вносится значение 00001, а в поле PosID — значение 2.
Удаление строк Для удаления строк таблицы используется команда DELETE. Например, для удаления всех строк таблицы FAMILY необходимо выполнить оператор: DELETE FROM FAMILY
Также можно применять ключевое слово WHERE для удаления строк, удовлетворяющих определенным условиям: DELETE FROM FAMILY WHERE ID = 0
Подключение к базе данных и отключение от базы данных Для подключения к базе данных InterBase используется команда CONNECT, имеющая в упрощенном варианте следующий синтаксис:
Что дальше?
165
CONNECT " e : \ p r o g r a r a s \ s t a f f \ b a s e \ s t a f f . g d b " USER "SYSDBft." PASSWORD "masterkey"
Для отключения от базы данных используется команда DISCONNECT ALL.
•
Что дальше? В следующей главе будет продолжена разработка базы данных приложения "Персонал". При этом будут рассмотрены некоторые элементы баз данных, подобные хранимым процедурам и триггерам, и соответствующие особенности диалекта SQL для InterBase. . .
• • -
•-
•
-
_
.
—
Глава 5
Базы данных InterBase В этой главе будет продолжена разработка базы данных staff .gdb для приложения "Персонал". Основное внимание будет уделено элементам базы данных, характерным для SQL-сервера InterBase, например, таким понятиям как триггер, генератор или хранимая процедура. Сразу следует отметить, что материал, изложенный в этой главе, к локальным базам данных почти не применим. Те редкие исключения, в которых рассматриваемые SQL-команды все-таки могут быть использованы при работе с локальными таблицами, будут выделены в примечаниях. Напоминаем, что для администрирования базы данных InterBase используется программа IBConsole. В этой программе открывается окно Interactive SQL. При помоши этого окна можно выполнять SQL-команды, рассматриваемое в данной главе. Кроме того, по ходу изучения представленного материала рекомендую исследовать изменения, вносимые в базу данных s t a f f , gdb, при помощи древовидной структуры главного окна программы IBConsole. Такая методика позволяет лучше разобраться в том, как представленные примеры реализуются на практике, а также лучше освоить основы администрирования баз данных InterBase. Отметим также, что все описанные ниже программные функции можно было бы реализовать непосредственно в приложении. Тем не менее, более правильным считается подход, при котором значительная часть функциональности хранится в самой базе данных. Это позволяет организовать эффективную защиту данных от несанкционированного доступа, а также повышает производительность приложений, так как многие операции выполняются не на стороне пользователя, а на стороне сервера.
Генераторы Генератор (generator) — этр последовательно возрастающее число, которое может автоматически вставляться в столбец при помощи функции GEN_ID(). Генераторы часто используются для формирования уникальных значений столбцов, входящих в первичный ключ. База данных InterBase может содержать любое количество генераторов, и они могут использоваться или обновляться в любой транзакции. Создадим в базе данных staff .gdb три генератора, которые будут использоваться для занесения значений в первый столбец (столбец идентификатора) таблиц STAFF, DEPS и POSS.
Генераторы
167
Выполните следующие команды SQL: CREATE GENERATOR STAFF_ID_GEN; CREATE GENERATOR DEPS_ID_GEN; CREATE GENERATOR POSS ID GEN;
В результате в базе данных будет создано три генератора. Для того чтобы убедиться в этом, перейдите в основное окно программы IBConsole и выделите в древовидной структуре элемент Interbase Servers >• Local Server >• Databases >• STAFF.GDB >• Generators (рис. 5.1). Ниже по тексту подобные ссылки сокращены до двух последних элементов. 1*1 iBConsole Console
View
in
•MHHHHHBE
Serve Database
- <*'
Took Windows H*>
- Д| IntefBa*
Nairn
-и *§ La -alSevB ? £ Databases % STAFF GDB
• pa
! Cn^entVAig
% DEPS ID GEN
0
fy POSS_ID_GEN
0
% STAFF.ID.GEN 0
Э Domaim QD Tinte
i
fSR Indnm Views P "t - -1^
i
"
:
- ^fjj Eidemai Fincbons
.
% EffiBSffl
'
9 Blob Flls;
-
* "•*" CnWcate:
BK
т
i
SaiVKLog
• '
Cat*«:5'AH=.SDB
^j
;
A
Рис. 5.1. В базе данных s t a f f .gdb созданы три генератора Прямой SQL-команды удаления генераторов не существует, поэтому для удаления какого-либо генератора из базы данных используют "обходной маневр". Дело в том, что кроме таблиц, создаваемых пользователями, в базах данных InterBase существуют также и служебные таблицы, создаваемые автоматически самим SQL-сервером. Для того чтобы увидеть эти таблицы в программе IBConsole, необходимо выполнить команду View >- System Data. Ссылки на создаваемые генераторы заносятся в служебную таблицу RDB$GENERATORS. Следовательно, для того чтобы удалить какой-либо генератор, например POSS ID GEN, достаточно выполнить такую SQL-команду: DELETE FROM RDB$GENERATOR WHERE RDB$GENERATOR NAME = 'POSS ID GEN 1
После того, как генераторы созданы, необходимо каким-то образом увеличивать их значение при добавлении новых строк в соответствующие таблицы. Для этого используются триггеры.
168
Глава 5. Базы данных InterBase
Триггеры Триггер (trigger) — это программа на языке SQL, которая выполняется при определенной операции над конкретной таблицей. Триггеры ассоциируются с такими операциями как вставка (INSERT), обновление (UPDATE) и удаление (DELETE) данных. Создадим несколько триггеров в базе данных s t a f f . gdb. 1. Создадим триггер, удаляющий из таблиц FAMILY и JOBS все строки, соответствующие записи из таблицы STAFF: SET TERM
Л
;
CREATE TRIGGER STAFFDelete FOR STAFF " BEFORE DELETE AS BEGIN DELETE FROM FAMILY WHERE EmpID = OLD.ID; DELETE FROM JOBS WHERE EmpID = OLD.ID; END
Рассмотрим некоторые составные элементы триггеров. Ключевое слово BEFORE указывает на то, что триггер вызывается перед выполнением соответствующей операции (INSERT, UPDATE или DELETE). При создании триггеров, выполняемых после некоторой операции, вместо слова BEFORE используется слово AFTER. Обратите также внимание на использование переменной OLD. Эта переменная служит для ссылки на значения, которые имели поля перед выполнением операции UPDATE или DELETE. Существует еще одна специальная переменная NEW, которая в противоположность переменной OLD служит для ссылки на новые значения, вставляемые в столбцы командами INSERT или UPDATE. ПРИМЕЧАНИЕ Как уже указывалось в разделе "Добавление данных" предыдущей главы, с помощью оператор SET TERM устанавливается разделитель операторов SQL. В качестве такого разделителя по умолчанию используется символ ";". Однако, операторы, входящие в триггер, также должны разделяться символом ";". По этой причине для выполнения SQLоператора CREATE TRIGGER необходимо предварительно определить другой разделитель (в данном случае используется символ "Л"), иначе программа IBConsole выдаст сообщение об ошибке, и триггер создан не будет. После создания всех триггеров можно опять установить прежний разделитель при помощи команды: SET TERM ; \ 2. Теперь создадим три триггера для таблиц STAFF, DEPS и POSS, вызывающие функцию GEN_ID для увеличения текущего значения соответствующего генератора. SET TERM Л ; CREATE TRIGGER STAFFInsert FOR STAFF
-
Индексы
169
BEFORE INSERT POSITION 0 AS BEGIN NEW.ID = GEN_ID(STAFF__ID_GEN, 1); END"
CREATE TRIGGER DEPSInsert FOR DEPS BEFORE INSERT POSITION 0 AS BEGIN NEW.DeptID = GEN_ID(DEPS_ID_GEN/ 1); END"
CREATE TRIGGER POSSInsert FOR POSS BEFORE INSERT POSITION 0 AS BEGIN NEW.PosID = GEN_ID(!>OSS__ID_GEN, 1);
END"
Ключевое слово POSITION используется для определения порядка выполнения нескольких триггеров, связанных с одной и той же операцией. В данном случае POSITION 0 означает, что триггер выполняется первым среди всех триггеров, связанных с операцией INSERT. В каждом из трех представленных выше триггеров в поле идентификатора добавляемой строки (NEW. ID) вносится значение соответствующего генератора, увеличенное на единицу. В качестве второго параметра функции GEN_ID можно передавать числа больше единицы.
Индексы Для создания индексов в InterBase используется команда CREATE INDEX, уже рассмотренная в разделе "Индексы" главы 4. Рекомендуется создавать индексы для тех столбцов, которые часто используются при выборе данных командой SELECT, так как это значительно ускоряет процесс поиска необходимых строк. С другой стороны, создавать слишком много индексов также не желательно, так как вместо повышения производительности они могут привести к обратному результату — замедлению выборки данных. В частности, не имеет смысла создавать индексов для небольших таблиц. » Вопросы оптимизации баз данных рассматриваются в главе 9.
В InterBase поддерживается полезный механизм временного отключения индексов для ускорения операций по модифицированию таблицы. Для этого индекс переводится в неактивное состояние с последующим повторным созданием. Благодаря этому можно быстро добавлять в таблицу стразу много строк без необходимости многократного перестраивания индекса для каждой новой строки. Для отключения индексов используется следующая команда:
170
Глава 5. Базы данных InterBase
ALTER INDEX имя индекса INACTIVE Для повторного создания индекса используется следующая команда: ALTER INDEX имя индекса ACTIVE При подключении индекса он строится заново.
Ограничения Ограничением (constraint) называется механизм определения допустимых данных столбца. Ограничения могут налагаться при создании таблицы с помощью команды CREATE TABLE или впоследствии с помощью команды ALTER TABLE ADD. Если ограничение используется индексом, то перед отключением индекса вначале необходимо снять ограничение при помощи команды ALTER TABLE DROP. Рассмотрим существующие виды ограничений. .
Первичный ключ Этот вид ограничений уже был рассмотрен в разделе "Разработка структуры таблицы фотоальбома" главы 3. В первичном ключе определены столбцы, которые содержат значения, уникальные для каждой строки таблицы. Для одной таблицы можно определить только один первичный ключ. Например, для таблицы REGIONS первичный ключ был установлен следующим образом: CREATE TABLE REGIONS ( Zip integer NOT NULL, PRIMARY KEY,
В данном случае в первичный ключ входит только столбец zip. Эту же операцию можно было бы выполнить при помощи команды CREATE TABLE несколько иначе: CREATE TABLE REGIONS ( Zip integer NOT NULL, City varchar(20) NOT NULL, PRIMARY KEY ( Z i p ) ) ПРИМЕЧАНИЕ Этот же способ используется для создания первичных ключей локальных таблиц. Кроме того, для изменения структуры, включая наложение ограничений, можно после создания таблицы использовать команду fiLTER TABLE: ALTER TABLE REGIONS ADD PRIMARY KEY (Zip)
Ограничения
171
Внешние ключи Внешний ключ (foreign key) определяет столбец одной таблицы, значения которого должны существовать во второй таблице, которая называется внешней. В. отличие от первичного ключа, внешний ключ не является уникальным индексом строки. Уникальными являются ключевые столбцы во внешней таблице. Добавление в таблицу внешнего ключа приводит к автоматическому созданию вторичного индекса по ключевым столбцам. Также как и первичный, внешний ключ можно определять в команде CREATE TABLE или после создания таблицы при помощи команды ALTER TABLE ADD FOREIGN KEY.
_ 1. Создадим внешний ключ для таблицы STAFF:
ALTER TABLE STAFF ADD FOREIGN KEY (PosID) REFERENCES POSS
Это ограничение назначает столбец PosID внешним ключом, который ссылается на аналогичный столбец таблицы POSS. Это означает, что введенные в таблице STAFF идентификаторы должностей должны существовать в таблице POSS, а также, что идентификаторы, использующиеся в таблице STAFF, нельзя удалить из таблицы POSS. Такая возможность закрепления связи между двумя таблицами называется ссылочной целостностью (referential integrity). Обеспечение целостности ссылок таблиц друг на друга выполняется при их создания или объявлении на уровне сервера базы данных, а не на уровне приложения. 2. Создадим аналогичный внешний ключ для поля DepID: ALTER TABLE STAFF ADD FOREIGN KEY (DepID) REFERENCES DEPS(DeptID)
В данном случае названия полей в таблицах STAFF и DEPS не совпадают, поэтому после названия таблицы DEPS указывается имя столбца, с которым связан внешний ключ таблицы STAFF. 3. Напоследок определим для таблицы STAFF еще один внешний ключ — по почтовому индексу: ALTER TABLE. STAFF ADD FOREIGN KEY (Zip) REFERENCES REGIONS
Проверка значений Ограничение на значения также может накладываться как при создании таблицы, так и после. Для этого используется ключевое слово CHECK. Например, можно было бы наложить при создании таблицы STAFF ограничение на значение, занесенное в поле Zip: CREATE TABLE
(...,
i
CHECK (Zip BETWEEN I AND 99999))
172
Глава 5. Базы данных InterBase
1. Давайте создадим это ограничение при помощи команды ALTER TABLE: ALTER TABLE STAFF. ADD CONSTRAINT INVALID ZIP CHECK
(Zip BETWEEN 1 AND 99999)
2. Добавим также ограничение на значение зарплаты: ALTER TABLE STAFF ADD CONSTRAINT INVALID SALARY CHECK ( S a l a r y >= 0)
Обратите внимание на негативный смысл имен, выбранных для ограничений (например, фраза "INVALID ZIP" означает "некорректный почтовый индекс"). Такие имена необходимы для того, чтобы при выдаче сообщения о нарушении установленного ограничения пользователь мог определить источник проблемы по одному лишь имени ограничения. 3. Определим еще одно ограничение для таблицы JOBS. Его смысл заключается в том, что поле-индикатор строк, соответствующих должностям в текущей организации, должен содержать только 1 или 0. ALTER TABLE JOBS ADD CONSTRAINT INVALID CURORG_FLAG CHECK (CurOrg IN ('!', '0'))
Хранимые процедуры Хранимая процедура (stored procedure) — это скомпилированная программа произвольной длины на языке SQL, которая хранится в базе данных вместе с другими объектами. Хранимые процедуры InterBase подразделяются на две основные разновидности: процедуры выбора (select procedure) и выполняемые процедуры (executable procedure). Процедуры выбора используются в операторе SELECT вместо таблицы, и, следовательно, должны возвращать наборы данных, тогда как для выполняемых процедур это не обязательно. Хранимые процедуры создаются командой CREATE PROCEDURE, а удаляются командой DROP PROCEDURE.
Процедуры выбора Создадим в базе данных staff .gdb процедуру формирования списка подчиненных подразделений. В данном случае подразумевается, что предприятие имеет иерархическую структуру подразделений. В таблице DEPS в столбце ParentDeptID указывается идентификатор "родительского" подразделения. Если родительского подразделения не существует (элементы на вершине иерархии), тогда в поле ParentDeptID содержится значение 0. SET TERM "
;
CREATE PROCEDURE ChildDeptsList (pDeptID integer) RETURNS (rDeptFullName varchar(100)) AS BEGIN
Хранимые процедуры
173
FOR SELECT DeptFullName FROM DEPS WHERE ParentDeptiD = :pDeptID INTO :rDeptFullName DO SUSPEND; END
В эту процедуру передается параметр pDeptiD, соответствующий идентификатору "родительского" подразделения, а в качестве результата возвращается список подчиненных подразделений. В процедурах выбора возвращаемые данные определяются при помощи ключевого слова RETURNS. Параметры, перечисленные после имени процедуры, называются входными (input), а перечисленные после ключевого слова RETURNS — выходными (output). При обращении как к входным, так и к выходным параметрам в теле процедуры (между ключевыми словами BEGIN и END) перед их именами ставится символ ":". Для возвращения строк результата в вызывающую программу используется конструкция FOR SELECT . . . DO. Команда SUSPEND приостанавливает выполнение процедуры до следующего запроса. Перед приостановкой выполнения возвращаются значения, присвоенные выходным параметрам. •
Выполняемые процедуры Выполняемые процедуры отличаются от процедур выбора только тем, что в них может не использоваться оператор RETURNS. Создадим процедуру для удаления данных о сотруднике. SET TERM " ;
CREATE PROCEDURE DeleteEmp (pEmpID integer) AS BEGIN DELETE FROM STAFF WHERE ID = :pEmpID; END
Объявление переменных В хранимых процедурах можно объявлять переменные. Для этого используется следующий синтаксис: CREATE PROCEDURE имя__ процедуры -о
VARIABLE имя_переменной тип_данных
[,
-..]
BEGIN
Обращение к переменным в теле процедуры осуществляется так же, как и к параметрам — через двоеточие.
174
Глава 5. Базы данных InterBase
Выполнение хранимых процедур Процедуры выбора используются в операторах SELECT. Например, для выборки всех подразделений, соответствующих идентификатору 1, можно использовать следующий оператор: SELECT * FROM DeptsList(1) Выполнимые процедуры выполняются по команде EXECUTE PROCEDURE. Так, для удаления из базы данных информации о сотруднике с идентификатором 5 можно воспользоваться следующей командой: _EXECUTE_PROCEDURE DeleteEmp 5
Обратите внимание на то. что- при выполнении процедуры список параметров отделяется от названия процедуры пробелом.
Исключения Исключения (exception) — это механизм сообщения об ошибках, определенных пользователем. Этот механизм реализуется при помощи хранимых процедур и триггеров. Для определения нового исключения в InterBase используется команда CREATE EXCEPTION, а для удаления — команда DROP EXCEPTION. Для активизации и генерирования исключения используется команда EXECUTE. Создадим три исключения, которые происходят в тот момент, когда кто-то пытается удалить из таблиц DEPS, POSS или REGIONS строки, ключевые значения которых используются в таблице STAFF. ПРИМЕЧАНИЕ Подобное удаление в любом случае невозможно выполнить, так как прежде с этими таблицами были связаны соответствующие столбцы таблицы STAFF при помощи внешних ключей. Тем не менее, исключения в случае выполнения некорректной операции позволяют отобразить пользователю удобочитаемое сообщение. CREATE EXCEPTION Dept_Is_Busy 'На это подразделение есть ссыпка в таблице сотрудников. Удаление н е в о з м о ж н о . ' ; CREATE EXCEPTION Pos_Is_Busy 'На эту должность есть ссылка в
таблице сотрудников. Удаление невозможно.'; CREATE EXCEPTION Zip_Is_Busy 'На этот регион есть ссылка в таблице сотрудников. Удаление невозможно.'
Создадим еще одно исключение, происходящее при удалении подразделения, которое имеет подчиненные подразделения. CREATE EXCEPTION Dept_Has_Children 'Это подразделение имеет подчиненные подразделения, в которых есть сотрудники. 1
Перейдите в главное окно программы IBConsole и выделите в древовидной структуре элемент STAFF.GDB >- Exceptions. На рис. 5.2 видно, что в базе данных s t a f f , gdb были определены четыре исключения. .
Ш lnoe«! И Views ^j ^IDied Procedues £r Eternal FucTions
Blob Fia 5
Me,
Log fJS Use;;
'
Dalaba«i STAFF .SOB
Рис. 5.2. В базе данных staff. gdb определены четыре исключения Теперь для вызова этих исключений необходимо создать соответствующие триггеры. Триггеры — это, по сути, особые хранимые процедуры, и в них также можно использовать конструкции процедур (включая объявление переменных). SET TERM "
;
CREATE TRIGGER DEPSDelete FOR DEPS BEFORE DELETE AS DECLARE VARIABLE CountOfRefs integer; BEGIN SELECT COUNT(ID) FROM STAFF WHERE DepID = OLD.DeptID INTO :CountOfRefs; IF (:CountOfRefs >-0) THEN EXCEPTION Dept_Is_Busy; 5ELECT COUNT(ID) FROM STAFF WHERE DepID IN (SELECT DeptID FROM DEPS WHERE ParentDeptID = OLD.DeptID) INTO :CountOfRefs;. IF (:CountOfRefs > 0) THEN EXCEPTION Dept_Has_Children;
END" CREATE-TRIGGER POSSDelete FOR POSS BEFORE DELETE AS
176
Глава 5. Базы данных InterBase
DECLARE VARIABLE CountOfRefs integer; BEGIN SELECT COUNT(ID) FROM STAFF WHERE PosID = OLD.PosID INTO :CountOfRefs; IF. (:CountOfRefs > 0) THEN EXCEPTION Pos_Is__Busy; END" CREATE TRIGGER REGIONDelete FOR REGIONS BEFORE DELETE AS DECLARE VARIABLE CountOfRefs integer; BEGIN SELECT COUNT(ID) FROM STAFF WHERE Zip = OLD.Zip INTO :CountOfRefs; IF (:CountOfRefs > 0) THEN EXCEPTION Zip_Is_Busy; END
Представления Представление (view) таблицы SQL создается с использованием оператора SELECT, после чего с полученным набором данных можно обращаться как с таблицей, и применять к нему же операторы SELECT. В самом представлении не хранится никаких данных — это всего лишь маленькая SQL-программа. Представления создаются при помощи команды CREATE VIEW. Создадим представление FullList, которое выбирает всю информацию о сотрудниках: CREATE VIEW FullList AS SELECT staff.ID, staff.LastName, staff.FirstName, staff.FatherName, staff.Zip, regions.Area, regions.Region, regions-City, staff.Street, staff.House, staff.Tel, staff.TaxCode, staff.TabNum, staff.BirthDate, staff.DepID, deps.DeptShortName, deps.ParentDeptID, deps.Parents, staff.Posld, poss.PosShortName, poss.PosLevel, staff.BornPlace, staff.Salary, staff.PasspNum, staff.PasspDate, staff.Photo FROM staff INNER JOIN regions ON staff.Zip = regions.zip INNER JOIN deps ON staff.DepID = deps.DeptID INNER JOIN poss ON staff.PosID = poss.PosID
Роли
177
Это представление выбирает информацию не только из таблицы STAFF, но и соответствующие данные по каждому сотруднику из таблиц REGIONS, DEPS и POSS. Например, в каждой строке представления FullList будет не только идентификатор подразделения, но и его сокращенное название, и идентификатор "родительского" подразделения. Теперь к созданному представлению можно обращаться как к обычной таблице, например: SELECT * FROM FullList ORDER BY LastName,
FirstName, FatherName
ПРИМЕЧАНИЕ Сортировка строк при помощи конструкции ORDER BY непосредственно в операторе CREATE VIEW не используется.
Роли Роли (role) используются для ограничения доступа к отдельным элементам базы данных. Пользователю (или группе пользователей) можно определить роль, и таким образом обеспечить необходимый уровень защиты информации при подключении к базе данных. » Подробно вопрос ролей и прав доступа рассматривается в главе 8.
Другие элементы InterBase В этой и предыдущей главах были рассмотрены такие элементы баз данных InterBase как таблицы, индексы, хранимые процедуры, триггеры, генераторы и исключения. Существует еще несколько понятий InterBase, которые в базе данных s t a f f . g d b использоваться не будут. Рассмотрим их. Если эти подробности вас не интересуют, можете пропустить этот раздел, и перейти к изучению материала следующей главы.
Домены Домен (domain) -- это объявление типа столбца, которое может использоваться во всей базе данных. Например, в базе данных staff .gdb можно было бы объявить следующий домен: CREATE DOMAIN StrlOO AS varchar(100) В дальнейшем этот домен можно было бы использовать в качестве типа столбцов DeptFullName И ParentDeptlDs таблицы DEPS, столбцов Organization, Pos И StopCauses таблицы JOBS, а также столбца PosFullName таблицы POSS. Например: CREATE TABLE (DeptID integer NOT NULL PRIMARY KEY,
178
Глава 5. Базы данных InterBase
DeptFullName StrlOO,
Внешние функции В InterBase пользователи могут расширять диалект SQL путем введения собственных функций. Функции определяются в совместно используемых внешних библиотеках (в системе Windows это динамические библиотеки типа DLL). Эти функции объявляются для использования в базе данных InterBase с помощью команды DECLARE EXTERNAL FUNCTION. С этого момента функция помещается в базу данных и ведет себя точно также как любая другая внутренняя функция. Приведем пример исходного кода библиотеки DLL с определением функции конкатенации (слияния) двух строк. library StrFLib; uses SysUtils, Classes, System; function ConcatStr(var Strl, Str2: string): string; far cdecl export; begin Result := Strl + Str2; end; '
exports ConcatStr; begin end.
Рассмотрим пример команды SQL, которая делает описанную функцию доступной для использования в InterBase. DECLARE EXTERNAL FUNCTION ConcatStr varchar(100), varchar(lOO) RETURNS varchar (200) BY VALUE ENTRY POINT 'ConcatStr' MODULE NAME 'StrFLib.dll' Теперь функцию ConcatStr можно использовать, например, так: SELECT * FROM TABLE1 WHERE Columnj^= ConcatStr(Column2,Со1шппЗ)
Для удаления объявления внешней функции из базы данных используется команда DROP EXTERNAL FUNCTION.
Курсоры С помощью курсоров (cursor) можно работать с отдельными строками таблицы и выполнять построчные операции. Обычно курсоры не пишутся на языке SQL, но создаются автоматически средствами доступа к данным. Например,
Сценарий создании базы данных staff.gdb
179
средства dbExpress, которые будут рассматриваться в следующей главе, создают так называемые однонаправленные курсоры — наборы данных, в которьк можно перемещаться от одной строки к другой только в одном направлении. Курсоры можно также использовать в хранимых процедурах. Над курсорами выполняются четыре основные операции: объявление, открытие, извлечение данных и закрытие. С помощью курсоров можно также модифицировать и удалять строки таблицы. Объявление курсора содержит операцию SELECT и список модифицируемых столбцов (для модифицируемых курсоров). Рассмотрим пример курсора. DECLARE STAFF_SELECT CURSOR FOR SELECT * FROM STAFF
Прежде, чем считывать строки, используя курсор, его необходимо открыть. Для инициирования запроса, содержащегося в объявлении курсора, используется команда OPEN. OPEN STAFF SELECT
По команде OPEN никакие данные в приложение не передаются. Для этого необходимо использовать команду FETCH. FETCH STAFF_SELECT
По этой команде из возвращаемого курсором набора данных извлекается одна строка. Все последующие вызовы FETCH приводят к извлечению очередных строк.
Сценарий создания базы данных staff.gdb Часто бывает удобно помещать операторы языка определения данных (DDL Data Definition Language), в том числе хранимые процедуры, в файлах сценариев (script) SQL. Эти файлы можно редактировать в любом текстовом редакторе. Сценарии должны содержать все необходимые операторы USE и SET TERM. Выполнение сценариев осуществляется при помощи специальных средств, например, в программе IBConsole для этого служит окно Interactive SQL. SI Полный сценарий создания базы данных staff.gdb находится на прилагаемой х книге дискете в файле CreateStaff.txt.
Что дальше? Итак, в этой главе были кратко рассмотрены основные элементы баз данных InterBase. Следует отметить, что рассмотренные примеры не отображают всех возможностей диалекта языка SQL InterBase. Для освоения всех возможностей именно этого языка, например, можно поработать с технической документацией. Но любая даже самая лучшая книга не принесет успеха без долгих часов практической работы. Поэтому настоятельно рекомендуем самостоятельно ис-
180
Глава 5. Базы данных InterBase
следовать структуру базы данных staff .gdb при помощи программы IBConsole после внесенных в этой главе изменений. В следующей главе будут рассмотрены методы подключения к базе данных, и компоненты dbExpress, которые используются для извлечения данных из SQLсерверов. Также в следующей главе мы приступим непосредственно к разработке приложения "Персонал". . "
•
-
. "
" -
Глава 6
Доступ к SQLсерверам из приложений Delphi 7 'г
ят • • •
Приложения, предназначенные для взаимодействия с SQL-серверами, состоиз следующих элементов: элементов пользовательского интерфейса; компонентов, отвечающих за работу с наборами данных; компонентов, соединяющих перечисленные выше элементы друг с другом и с самой базой данных.
Несмотря на то, что в приложениях могут использоваться различные схемы взаимодействия компонентов и серверов баз данных, все их можно описать при помощи обобщенной схемы, представленной на рис. 6.1. Как показано на рис. 6.1, компоненты, предназначенные для работы с наборами данных, а также компоненты, которые используются для подключения наборов данных к другим частям приложения, лучше размещать в модулях данных. Это делает приложение более гибким, и, кроме того, один и тот же модуль данных можно использовать в различных приложениях. t Модуль данных Пол ьзовательский интерфейс
Источник данных (TDataSource)
Набор fc. f данных (TDaiaScl)
Подключение к базе данных
Рис. 6.1. Обобщенная схема взаимодействия приложения с SQL-сервером
Подключение к серверам баз данных Естественно, разработчикам баз данных в первую очередь хочется знать, как с помощью Delphi установить соединение с собственноручно созданной базой данных. Для этого используется несколько методик.
Способы подключения к базе данных Компоненты, предназначенные для работы с наборами данных, должны подключаться к SQL-серверам. Обычно для этого используют какой-либо компонент соединения (подключения к базам данных). Каждому из этих компонентов соот-
182
Глава 6. Доступ к SQL-серверам из приложений Delphi 7
ветствует класс, производный от класса TCustornConnection. Для различных Видов баз данных поддерживаются следующее типы компонентов подключения. • Компонент Database предназначен для подключения наборов данных средствами BDE. Этот вид подключения будет кратко рассмотрен в следующем разделе. • Компонент ADOConnection предназначен для подключения таких баз данных ADO как Microsoft Access и Microsoft SQL. В этой книге подключение ADOConnection не рассматривается. • Компонент SQLConnection предназначен для подключения наборов данных, ориентированных на использование средств dbExpress. Наборы данных dbExpress являются однонаправленными. Это означает, что в них можно перемещаться от записи к записи только в одном направлении. К таким наборам данных относятся следующие компоненты: SQLDataset, SQLTable, SQLQuery и SQLStoredProc. В этой главе, в основном, рассматривается именно этот способ подключения. • Компонент iBDatabase предназначен для подключения наборов данных, ориентированных на использование средств InterBase Express. Специально InterBase Express в этой книге не рассматривается, поскольку большая часть его функциональных возможностей напоминает подключения других видов. Каждый из этих компонентов обладает общими функциями, реализованными В классе TCustomConnection. • Подключение к базе данных и отключение от нее. • Регистрация пользователя при подключении и поддержка установленного защищенного соединения. • Работа с наборами данных.
Подключение к базе данных при помощи компонента Database Для подключения к SQL-серверам при помощи средств BDE можно воспользоваться компонентом Database, расположенным на вкладке BDE. Основными свойствами ЭТОГО компонента ЯВЛЯЮТСЯ AliasName, DatabaseName, LoginProrapt И Params. • В свойстве AliasName можно указать предварительно созданный псевдоним BDE, соответствующий той или иной базе данных. • В свойстве DatabaseName указывается непосредственно название базы данных. Если свойство AliasName не содержит никакого значения, тогда в свойстве DatabaseName должен быть указан полный путь к файлу базы данных. • Если свойство LoginPrompt имеет значение False, то при подключении к базе данных не используется явный запрос на ввод имени пользователя и пароля. В этом случае имя пользователя и пароль должны быть определены в списке параметров Params. Если же свойство LoginProrn.pt имеет значение
Разработка приложения "Персонал"
183
True, то при подключении к базе данных будет выдан запрос на ввод имени пользователя и пароля, независимо от того, какие значения указаны в свойстве Params. В качестве примера можно рассмотреть подключение к базе данных employee.gdb, входящей в поставку Delphi. Этой базе данных соответствует псевдоним BDE iBLocal. Таким образом, для подключения к ней достаточно присвоить свойству Database. AliasNarne значение IBLocal, свойству Database. DatabaseName значение employee . gdb, а затем установить подключение, присвоив свойству Connected значение True.
Доступ к базам данных при помощи средств dbExpress Технология dbExpress была введена компанией Borland в Delphi 6. Она обладает тремя важными преимуществами. • Во-первых, средства dbExpress гораздо проще с точки зрения установки по сравнению с их предшественником BDE. • Во-вторых, доступ к SQL-серверам через dbExpress намного эффективнее, чем при помощи BDE. Для организации эффективного доступа к данным в dbExpress используются однонаправленные наборы данных. • В-третьих, технология dbExpress является межплатформенной. Это означает, что разработанные приложения могут использоваться в среде Kylix на платформе Linux.
Однонаправленные наборы данных Суть однонаправленных наборов данных состоит в том, что для них не выделяется область оперативной памяти (буферизация или кэширование) с целью организации навигации или модификации. В отличие от dbExpress, в BDE использовались двунаправленные наборы данных с буферизацией в оперативной памяти. Однонаправленные наборы данных более быстродействующие, однако они обладают некоторыми ограничениями. • При навигации поддерживаются только методы First () и Next ( ) . Попытка вызова метода Last ( ) или Prior ( ) приведет к возникновению исключения. • Невозможно изменить данные, так как для них не выделяются буферы редактирования. Тем не менее, для редактирования однонаправленных наборов данных можно использовать другие компоненты. >» Один из таких компонентов, SimpleDataSet, будет рассмотрен в конце этой главы.
• Не поддерживается фильтрация, потому что при работе с однонаправленными наборами данных не создается буферов для множества записей.
Разработка приложения "Персонал" В предыдущих двух главах была разработана база данных приложения "Персонал", а теперь приступим непосредственно к разработке этого приложения.
184
Глава 6. Доступ к SQL-серверам из приложений Delphi 7
1. Запустите Delphi 7. 2. Создайте новый проект, выполнив команду File >- New Application. 3- Сохраните новый проект при помощи команды Fife >• Save A l l , указав в качестве имени программного модуля главной формы StfMain.pas, а в качестве имени файла проекта — Staff .dpr. 4. Добавьте в проект модуль данных, выполнив команду File >- New >• Data Module. В модуле данных будут размещены все компоненты, предназначенные для доступа к SQL-серверу и для работы с наборами данных. 5. Выполните команду Save All и укажите в качестве имени программного модуля StfDMod.pas. Сразу же добавим в проект еще одну форму, предназначенную для регистрации подключения к SQL-серверу. В ней будет указываться размещение базы данных, имя пользователя и пароль. 6. Для этого выполните команду Fite >- New >• Form, а затем — File >- Save, и укажите в качестве имени программного модуля stfLogin.pas. 7. В разделе interface программного модуля StfMain добавьте ссылки на модули StfDMod и StfLogin, а в том же разделе модуля StfLogin — ссылку на модуль StfDMod. ' 8. Присвойте свойству Name главной формы значение fmMain, а этому же свойству второй формы и модуля данных — значения fmLogin и dmStaf f соответственно. 9. Присвойте значения некоторым свойствам форм fmMain и fmLogin в соответствии с табл. 6.1. Таблица 6.1. Свойства форм fmMain И fmLogin Свойство
Пояснение
Значение
Свойства формы fmMain Caption
Персонал
Заголовок формы.
Position
poScreenCenter
Visible
False
При запуске приложения главная форма размещается в центре экрана. При запуске приложения главная форма отображается только после подключения к базе данных при помощи формы fmLogin.
Свойства формы fmLogin BorderStyle
bsDialog
Caption
Подключение
Форма не предусматривает изменение размеров. к
базе данных
Height
130
Position
poScreenCenter
Width
460
Высота формы в пикселях. Ширина формы в пикселях.
Разработка приложения "Персонал"
185
Форма fmLogin Окончательный вид формы fmLogin в режиме конструктора форм представ-
лен на рис. 6.2. ? Подкпючениекбаэедлнык
X Отменил.
Рис. 6.2. Форма fmLogin На этой форме будет размещено три компонента LabeledEdit и три компонента BitBtn, которые в палитре компонентов находятся на вкладке Additional. Расположите их на форме и присвойте значения их свойствам в соответствии с табл. 6.2. Таблица 6.2. Компоненты формы fmLogin Свойство
Значение
Пояснение
Компоненты LabeledEdit Свойство Name — имя компонента. 1. Name = laedDatabase 2. Name = laedUser 3. Name = laedPassword EditLabel . Caption База данных
Для laedDatabase
Пользователь
ДЛЯ laedUser
Пароль
ДЛЯ laedPassword
Left
8
Для laedDatabase и laedUser Для laedPassword
PasswordChar
164 *
Для laedPassword — СИМВОЛ, КОТОрЫЙ
отображается вместо символов, вводимых с клавиатуры. Top Width
24
Для laedDatabase
72 300
Для laedUser и laedPassword Для laedDatabase
145
Для laedUser И laedPassword
Компоненты BitBtn 1. Name=bbFind 2. Name = bbConnect 3. Name =bbCancel
—
.—
186
Глава 6. Доступ к SQL-серверам из приложений Delphi 7
Окончание таблицы 6.2 -
Свойство
Значение
Пояснение
Kind
bkCancel
ДляЬЬСапсе!
Caption
Найти данных
базу
ДЛЯ bbFind
Подключиться
Для bbConnect
Отменить
ДляЬЬСапсе!
Default
True
Enabled
False
Для bbConnect — кнопка, выбранная по умолчанию. Это означает, что нажатие клавиши <Enter> в форме равнозначно щелчку мышью на этой кнопке. Для bbConnect — пока не будут введены все параметры лодключения, кнопка недоступна.
Left
324
Top
12
Для bbFind
40
Для bbConnect
68
Для bbCancel
Width
120
Теперь укажите для свойства fmLogin.Activecontrol ссылку на компонент laedDatabase. Это означает, что при активизации формы fmLogin курсор будет автоматически устанавливаться в поле База данных.
Подключение к базе данных staff.gdb Для подключения к базе данных s t a f f . g d b воспользуемся компонентом \*SQLConnection, расположенным на вкладке dbExpress. Кроме того, потребуется вставить немного программного кода в модули stfLogin и stfDMod.
Компонент SQLConnection 1. Расположите в модуле данных компонент SQLConnection и присвойте его свойству Name значение s q l c S t a f f . Объекты класса TSQLConnection используют два конфигурационных файла: dbxdrivers.ini и dbxconnections.ini. Зги файлы при установке размещаются в каталоге \Program FilesXCommon Files\Borland Sharec!\DbExpress. • Файл dbxdrivers. ini содержит перечень всех установленных драйверов dbExpress и их параметры. • Файл dbxconnections.ini содержит перечень так называемых именованных соединении (named connections), которые по своей сути аналогичны псевдонимам BDE. В этом же файле хранятся и параметры именованных соедине-
Разработка приложения "Персонал"
187
ний. Во время выполнения приложения "Персонал" выбранный по умолчанию файл dbxconnections.ini использоваться не будет, так как подключение будет выполняться на основании введенных пользователем значений параметров соединения. 2. Для того чтобы отключить использование файла dbx connect ions. ini, свойству sqlcStaff.LoadParamsOnConnect необходимо присвоить значение False. Для инициализации параметров подключения используется свойство sqlcStaff. Params. В нем можно указать значения таких параметров как Database, User_Narae, Password и др. Это можно сделать как на этапе проектирования, дважды щелкнув мышью на поле свойства Params в инспекторе объектов, так и во время выполнения приложения. Используем последний вариант. Прежде, чем подключиться к конкретной базе данных, необходимо указать ее тип. 3. Выберите в инспекторе объектов для свойства sqlcStaff .Connection-Name значение iBConnection, соответствующее подключение к базе данных InterBase. В результате автоматически изменятся значения свойств DriverName, GetDriverFunc, LibraryName И Vendor Lib. Все ЭТИ свойства определяют драйвер и метод подключения. Изменять их вручную не требуется. В приложении "Персонал" для ввода имени пользователя и пароля используется специальная форма, поэтому автоматический вывод соответствующего запроса необходимо отменить. 4. Для этого присвойте свойству sqlcStaff .LoginPrompt значение False. Теперь организуем подключение к базе данных в форме fmLogin.
Программный код подключения mm
v
1. Расположите в модуле данных компонент OpenDialog с вкладки Dialogs палитры компонентов. Он будет использоваться для поиска базы данных s t a f f .gdb, когда пользователь щелкнет мышью в форме fmLogin на кнопке bbFind. 2. Присвойте компоненту OpenDialog имя od_l. 3. Перейдите к форме fmLogin и в конструкторе форм дважды щелкните мышью на кнопке bbFind. 4. В автоматически созданный метод bbFindClick введите следующие строки: with dmStaff.od_l do begin Filter := 'База данных InterBase|*.gdb'; FileName := laedDatabase.Text;
188
Глава 6. Доступ к SQL-серверам из приложений Delphi 7
Здесь выполняется поиск файла базы данных InterBase с расширением .gdb при помощи стандартного диалогового окна. Если пользователь подтверждает открытие выбранного файла, тогда имя этого файла с указанием полного пути заносится в поле База данных, а курсор устанавливается в поле Пользователь. Подключения нельзя выполнить (кнопка Подключиться недоступна), если не указано значение хотя бы одного из трех обязательных параметров: имя базы ДЗННЬЕХ, имя пользователя и пароль. Реализуем соответствующую проверку. 5. В режиме конструктора форм дважды щелкните мышью на компоненте laedDatabase и в созданный метод laedDatabaseChange введите следующий оператор: bbCormect.Enabled := (laedDatabase.Text <> ' ' ) and
(laedUser.Text <> ") and (laedPassword.Text о ' ' ) ; 6. Выделите на форме компоненты laedUser и laedPassword, перейдите в инспекторе объектов на вкладку Events. 7. Выберите для свойства OnChange ссылку на только что созданный метод laedDatabaseChange. Теперь после любых изменений, вносимых в одном из этих трех полей, будет выполняться проверка доступности кнопки Подключиться. Осталось только реализовать обработчик события OnClick кнопки bbConnect из формы fmLogin. 8. Создайте этот обработчик и введите в него следующие строки программного кода: with d m S t a f f . s q l c S t a f f do begin Params.Values['Database'] := laedDatabase.Text; Params.Values['User_Narae'] := laedUser.Text; Params.Values['Password'] := laedPassword.Text; Params.Values[•SQLDialect'] := '3' ; L
ModalResult := mrOK; except ' Screen.Cursor := crDefault; MessageDlg('Неверно указана база данных, '+ 'пользователь или пароль!',mtError, [mbOK],0) ,• laedDatabase.SetFocus; end;
Первая часть этого метода должна быть уже понятна: здесь выполняется инициализация четырех основных параметров подключения. ПРИМЕЧАНИЕ Значение четвертого параметра (SQLDialect) влияет на интерпретацию двойных кавычек, больших чисел и таких типов данных как DATE, TIME и TIMESTAMP. Для того чтобы все эти данные интерпретировались правильно, необходимо использовать третий диалект SQL. « См. также раздел "Создание базы данных" главы 4.
После инициализации try...except...end.
параметров
подключения
следует
конструкция
НЕМНОГО ТЕОРИИ Конструкции try...except используются для перехвата и обработки исключений, возникающих в процессе выполнения программного кода приложения. Если во время выполнения операторов, расположенных между ключевыми словами try и except возникает ошибка (например, при попытке деления на нуль), то сразу же происходит переход к блоку операторов, расположенных между ключевыми словами except и end. При этом операторы, расположенные в блоке try после оператора, приведшего к возникновению ошибки, не выполняются. В блоке except исключения можно обрабатывать в соответствии с их типами, например; Try except on EZeroDivide do HandleZeroDivide; end; EZeroDivide — это класс исключения, соответствующий делению на нуль. Все подобные классы являются производными от класса Exception, и в каждом из них определены свойства Message и HelpContext. Эти свойства можно использовать для формирования сообщений об ошибке, например: try except on E: Exception do MessageDlg(E.Message, mtError, [rabOk] , 0); end;
190
Глава 6. Доступ к SQL-серверам из приложений Delphi 7
В блоке try метода bbConnectClick указатель мыши изменяет свой вид на песочные часы, а затем выполняется подключение к базе данных, соответствующей компоненту sqlcStaff. Если параметры подключения были указаны верно, то будет установлено соединение с базой данных s t a f f .gdb, и указатель мыши опять примет форму, выбранную по умолчанию. После этого свойству fmLogin.ModalResult присваивается значение mrOk, в результате чего форма закрывается. Если же подключение будет неудачным, тогда выполняется блок операторов except, и форма не закрывается. .
Отображение формы fmLogin перед отображением формы fmMain
Основной формой приложения является форма fmMain, однако первой должна отображаться форма fmLogin. 44 см. также раздел "Главный файл проекта" в главе 1.
Подобная задача — один из тех редких случаев, когда необходимо вносить изменения в исходный код файла проекта. 1, Выполните команду Project >- ViewSource. В результате Б редакторе исходного кода будет открыт главный файл проекта staff .dpr. 2, Внесите в него изменения, вьщеленные в листинге 6.1 полужирным шрифтом. Листинг 6.1. Файл staff .dpr program Staff; uses Forms, Controls, StfMain in 'StfMain.pas' {fmMain}, StfDMod in 'StfDMod.pas' {dmStaff: TDataModule), StfLogin in 'StfLogin.pas1 {fmLogin}; !$R *.resf begin application.Initialize; Application.GreateForm(TfmMain, fmMain); Application.CreateForm{TfmLogin, fmLogin); Application.CreateForm(TdmStaff, dmStaff); if fmLogin.ShowModal = mrCancel then Application.Terminate else begin fmMain.Visible := True; Application.Run; end; end.
•
Разработка приложения "Персонал"
191
Обратите внимание на то, что в разделе uses необходимо добавить ссылку на модуль Controls, в котором объявлена константа mrCancel. При инициализации приложения форма fmMain невидима. Первой отображается форма fmLogin при помощи метода ShowModal. Если эта форма была закрыта любым способом, кроме щелчка мышью на кнопке Подключиться, тогда выполнение приложения прерывается вызовом метода Application.Terminate. В противном случае, при успешном подключении к базе данных s t a f f .gdb, открывается главная форма приложения. 3. Перед тем, как запустить приложение, создайте обработчик события fmMain.OnClose и добавьте в него следующую строку: dmStaff.sqlcStaff.Close;
В этой строке выполняется отключение приложения от базы данных staff .gdb при завершении его работы. 4. Сохраните проект и запустите приложение. 5. В форме fmLogin укажите расположение базы данных s t a f f .gdb, имя пользователя (sysdba) И пароль (masterkey).
6. Затем щелкните мышью на кнопке Подключиться, (рис. 6.3). Подявочжне к бак дамы* ,
_XJ
.
г
-
Найти бе рдяных j
п
п
Поцк»ЧТГЬСП
t^sdba
j .".»".
Jf QJ че»*пь
К 1
Рис. 6.3. Подключение к базе данных staff .gdb В результате будет выполнено подключение к базе данных s t a f f .gdb, после чего на экране отобразится форма fmMain.
Сохранение параметров приложения в системном реестре Windows Закройте приложение, а затем запустите его еще раз. Заметили одно неудобство? Путь к базе данных и имя пользователя приходится вводить заново. Для того чтобы избежать этого, можно сохранять параметры подключения базы данных к приложению в системном реестре Windows или в файле .ini. Для приложений, работающих в среде Windows, предпочтительнее использовать системный реестр. (Предполагается, что работа происходит именно в этой операционной системе.) НЕМНОГО ТЕОРИИ Системный реестр Windows — это особая база данных, в которой хранится конфигурация о параметрах компьютера и операционной системы. Для просмотра и редактирования системного реестра используется программа regedit.exe. расположенная в корневом каталоге Windows. Однако, лучше не вносите изменения в реестр вручную, так как это может привести к сбоям в работе программ или даже самой операционной системы.
192
Глава 6. Доступ к SQL-серверам из приложений Delphi 7
НЕМНОГО ТЕОРИИ Системный реестр по умолчанию состоит из пяти разделов, и параметры приложений обычно сохраняют в разделе — HKEY_CURRENT_USER. В Delphi для этого используется специальный класс TRegistry. Будем сохранять параметры приложения "Персонал" в разделе системного реестра HKEY_cURREHT_usER\Software\staff- Первыми двумя параметрами будут расположение базы данных s t a f f . g o b и имя пользователя, указанного при последнем подключении.
Функции чтения и записи информации системного реестра Последовательность действий при работе с системным реестром в приложениях Delphi очень проста. 1. Создать объект класса TRegistry. 2. Открыть или создать необходимый раздел реестра. 3. Записать или считать значение параметра (или нескольких параметров), хранимого в текущем разделе реестра. 4. Закрыть текущий раздел. 5. Удалить объект класса TRegistry. В приложении "Персонал" функции чтения и записи реализуем в модуле данных, так как к нему имеют доступ все остальные модули приложения. Для начала создадим функции чтения и записи значений строковых параметров. 1. Вставьте в интерфейсной части модуля StfDMod между объявлением класса модуля данных Tdmstaf f и ключевым словом var следующие объявления: procedure WriteStrParam(sName,sValue: string); function ReadStrParam(sName: string): string; 1
var
dmStaff: TdmStaff; » Отличия между процедурами и функциями рассмотрены в приложении А. 2. Создайте реализацию этих двух функций в разделе implementation того же модуля StfDMod: implementation {$R *.dfm} procedure WriteStrParam(sName,sValue: string); var Reg: TRegistry;• begin Reg := TRegistry.Create;
Разработка приложения "Персонал" with Rag do begin OpenKey('Software\Staff',True); WriteString(sName,sValue); Closekey; Free; end; end; function ReadStrParamfsName; string): string; var Reg: TRegistry; begin Reg := TRegistry.Create; with Reg do begin OpenKey('Software\Staff',True); Result := ReadString(sName); Closekey; Free ; end;
end; end.
•
В процедуру WriteStrParam передается имя параметра системного реестра и его значение, а в функцию ReadStrParam — только имя параметра. Метод TRegistry.OpenKey открывает указанный раздел реестра. Если такой раздел в реестре отсутствует, а в качестве второго параметра метода передается значение True, тогда этот раздел создается и открывается. Если второй параметр метода OpenKey имеет значение False, тогда раздел, указанный в первом параметре, должен обязательно существовать в реестре. Для записи и чтения значений указанного параметра в процедуре WriteStrParam используется метод TRegistry. WriteString, а В функции ReadStrParam — метод TRegistry.Readstring. Для того чтобы использовать класс TRegistry, укажите в разделе uses модуля StfDMod.pas ссылку на модуль Registry. 3. Вставьте необходимые дополнения в программный модуль формы fmLogin. Для начала добавьте две строки в обработчик события bbConnect .OnClick: try
Глава 6. Доступ к SQL-серверам из приложений Delphi 7
except
В результате при успешном подключении к базе данных в соответствующих параметрах реестра будет сохраняться информация о размещении файла staff .gdb и об имени пользователя. 4. Теперь создайте обработчик события fraLogin.onCreate и введите в него следующие строки: laedDatabase,Text := R e a d S t r P a r a m f ' Б а з а данных' laedUser-Text := R e a d s t r P a r a m ( ' П о л ь з о в а т е л ь ' } ;
Таким образом, при создании формы fmLogin в поля База данных и Пользователь заносятся значения соответствующих параметров, считанные из реестра. 5. Няшмвц, создайте обработчик события fmLogin.OnActivate и вставьте в него следующую строку: •| if laedDatabase.Text О '' then laedPassword. Set Focus;
Эта строка означает следующее: если при создании формы путь к базе данжъгх и имя пользователя считываются из реестра, тогда курсор устанавливается в поле Пароль. 6. Сохраните проект и запустите приложение. 7. Укажите путь к файлу s t a f f - g d b , имя пользователя и пароль, и выполните шэдоиочемве к базе данных. •S Посче "этого закройте приложение и запустите сю еще раз. Теперь размещение базы данных и имя пользователя должны будут считаться жз СИСТЕМНОГО реестра Windows.
Обзор компонентов dbExpress Перед тем, как продолжить разработку приложения "Персонал", кратко рас•смщрта 'остальные компоненты dbExpress. К ним относятся компоненты SffliLDHtaSe-t, SQLTable, SQLQuery, SQLStoredProc, SQLMonitor и SimpleDataSet. Функции компонентов SQLTable, SQLQuery и SQLStoredProc реализованы в компоненте SQLDataSet, поэтому отдельно они здесь не расс матри ваются. .
Компонент SQLDataSet
г
Компонент SQLDataset используется для извлечения данных из сервера при :гквдощи однонаправленного курсора. Такие наборы данных используются для отображения содержимого таблиц, а также результатов выполнения запросов ]или хранимых процедур.
Обзор компонентов dbExpress
195
Основными свойствами класса TSQLDataset являются CornmandType и CommandText. Значение свойства CommandType определяет характер использования содержимого свойства CommandText. Возможные значения свойства CommandType перечислены в табл. 6.3, Таблица 6.3. Возможные значения свойства CommandType CommandType
Содержимое Свойства CommandText
Ct Query
SQL-оператор.
CtStoredProc
Имя хранимой процедуры.
ctTable
Имя таблицы, расположенной на сервере баз данных. Для извлечения всех записей и полей этой таблицы компонент SQLDataset автоматически генерирует SQL-оператор SELECT.
Если свойство CommandType имеет значение ctQuery, то свойство CommandText содержит какой-то SQL-оператор. Например, это может быть оператор SELECT, предназначенный для возврата некоторого набора данных: SELECT * FROM STAFF. Если свойство CommandType имеет значение ctTable, TO свойство ComraandText содержит имя таблицы, расположенной на сервере баз данных. В этом случае поле свойства CommandText в инспекторе объектов примет вид раскрывающего списка. В случае подключения к SQL-серверу все SQLоператоры, необходимые для извлечения данных, генерируются автоматически. Если свойство CommandType имеет значение CtStoredProc, то свойство CommandText содержит имя хранимой процедуры. Для выполнения указанной хранимой процедуры лучше использовать метод TSQLDataSet.ExecSQLi), а не присваивать значение True свойству Active. Кроме того, метод ExecSQH) может быть использован в том случае, если свойство CommandType имеет значение ctQuery, и SQL-оператор не возвращает никакого набора данных (команды INSERT, UPDATE И Т.Д.)-
Извлечение метаданных Метаданные (metadata) — это информация о самой базе данных. Их также можно извлекать при помощи компонента SQLDataset. Для этого используется процедура TSQLDataset. SetSchemalnfo ( ) , в которой указывается требуемый тип информации. Объявление функции SetSchemalnfo имеет следующий вид: procedure SetSchemalnfo(
SchemaType:
TSchernaType;
SchemaObjectName, SchemaPattern: string ); В параметре SchemaType указывается тип извлекаемой информации, в параметре SchemaObjectName •- имя таблицы или процедуры в случае запроса информации о столбце или индексе, а в параметре SchemaPattern — маска SQLшаблона для фильтрации возвращаемого набора данных.
196
Глава 6. Доступ к SQL-серверам из приложений Delphi 7
В табл. 6.4 перечислены все типы информации о структуре, которые могут извлекаться при помощи процедуры SetSchemalnfo ( ) . Таблица 6.4. Возможные значения параметра ScheraaType Значение параметра
Описание
Schema Type
stNo Schema
Информация о структуре не извлекается. В место мета-( данных с сервера возвращается набор данных, соответствующий указанному запросу или хранимой процедуре.
stTables
Извлекается информация обо всех таблицах данных на сервере, которые удовлетворяют критерию, указанному в свойстве SQL-соединения ТаЫеЗсоре.
stSysTables
Извлекается информация обо всех системных таблицах. Не все SQL-серверы для хранения метаданных используют системные таблицы. В этом случае будет возвращен пустой набор данных.
stProcedures
Извлекается информация обо всех хранимых процедурах на сервере.
stColumns
Извлекается информация обо всех столбцах (полях) в указанной таблице.
stProcedureParams
Извлекается информация обо всех параметрах указанной хранимой процедуры.
stlndexes
Извлекается информация обо всех индексах для указанной таблицы.
Компонент SOLMonitor Компонент SQLMonitor очень полезен при отладке SQL-приложений. Он регистрирует все SQL-команды, которые обрабатываются компонентом типа TSQLConnection, указанным в свойстве SQLMonitor -SQLConnection. Свойство SQLMonitor .Tracelist содержит журнал команд, которыми обмениваются клиент и сервер баз данных. Это свойство является объектом класса, производного от класса TStrings, поэтому содержащуюся в нем информацию можно сохранить в файле или просмотреть в memo-поле.
ПРИМЕЧАНИЕ Для автоматического сохранения содержимого свойства TraceList можно использовать свойства FileName MAutoSave.
Компонент SimpleDataSet Компонент SimpleDataSet предназначен для организации двунаправленного перемещения по таблицам базы данной и редактирования их содержимого. Этот
Что дальше?
197
компонент использует dbExpress для извлечения данных, а затем размещает эти данные в оперативной памяти компьютера. Набор данных SimpleDataSet является клиентским. Это означает, что он объединяет в себе скорость и простоту реализации однонаправленных наборов данных и, в то же время, предоставляет пользователю возможность редактировать данные и произвольно перемешаться в таблице от строки к строке. Для связи компонента SimpleDataSet с SQL-сервером используется свойство Connection. Здесь можно либо указать ссылку на компонент SQLConnection, либо определить независимое подключение при помощи свойств Connection.ConnectionName И Connection.Params. Для идентификации типа извлекаемых данных используются свойства DataSet. Comma rid Type и DataSet-CommandText. Использование этих свойств аналогично использованию соответствующих свойств компонента SQLDataSet. Изменения, внесенные в данные при помощи компонента SimpleDataSet можно сохранить в базе данных, вызвав метод Applyupdates. Для разрешения проблем, связанных с ошибками сохранения данных на сервере, используется обработчик события OnReconcileError. Набор данных SimpleDataSet можно сохранять в отдельном файле на диске при помощи метода SaveToFile. При этом имя файла указывается в свойстве FileName, а формат файла (двоичный или XML) • • в качестве параметра Format самого метода SaveToPile. В дальнейшем сохраненный файл можно открыть при помощи метода LoadFromFile. Также следует отметить, что компонент SimpleDataSet поддерживает все операции, характерные для работы с локальными таблицами, включая индексацию, просмотр диапазона значений при помощи метода SetRange, поиск при помощи метода Locate и т.д. л Что дальше? ii
В этой главе был описан процесс подключения к SQL-серверу, а также рассмотрены компоненты dbExpress, используемые для извлечения и редактирования данных из базы данных. Кроме того, была начата разработка приложения "Персонал", которому и посвящена вся следующая глава.
-
'
. .
Глава 7
Приложение
"Персонал"
Представленное Б этой главе приложение "Персонал" достаточно сложное, хотя, конечно же, и не содержит всех функций, необходимых для полноценногорешения всех задач современного отдела кадров. Эта программа позволяет выполнять первичный учет сведений о сотрудниках, которые могут использовать в работе руководители подразделений. Известно, что всякую задачу можно решить несколькими способами — все зависит от склада мышления и профессионального уровня разработчика, а также от его творческих способностей. Подходы, которые использовались в приложении "Персонал" для создания интерфейса и организации взаимодействия с базой данных, конечно же, не являются единственными и исключительными. Основная цель этой главы — дать представление о методах разработки приложений баз данных в Delphi 7 с использованием средств dbExpress. Кроме того, в приложении "Персонал" используются некоторые полезные компоненты, которые в этой книге еще не рассматривались, например TreeView и ValueListEditor. Поскольку приложение "Персонал" довольно большое, не будем рассматривать процесс разработки пользовательского интерфейса с такой тщательностью, как в предыдущих главах. Основные элементы форм будут представлены в листингах в текстовом виде, как в редакторе исходного кода в режиме просмотра формы View as Text. « См. подробнее в разделе "Файлы форм" главы 1. Кроме того, в листингах исходного кода программных модулей представлено достаточно комментариев, поясняющих работу методов и процедур.
Постановка задачи Приложение "Персонал" имеет следующие характеристики. • Структура подразделений должна быть представлена в виде древовидной иерархии. • Просмотр данных о сотрудниках должен быть реализован как для отдельного подразделения, так и в виде полного списка. • Сортировка записей должна выполняться как по фамилиям, так и по уровню (старшинству) должностей.
•
Модуль данных
199
• Должна быть предусмотрена возможность поиска сотрудников по фамилии, по идентификационному коду, по табельному номеру, по телефону и по номеру паспорта (другими словами — по уникальному признаку). • Должно быть предусмотрено редактирование всех таблиц базы данных staff.gdb. • Должны быть реализованы средства редактирования параметров приложения, хранимых в системном реестре Windows. • Должны быть реализованы средства построения отчетов. Последний пункт будет рассмотрен в главах 10 и 11, а все остальные — в данной главе. "
Модуль данных В предыдущей главе было реализовано подключение к базе данных staff .gdb при помощи компонента SQLConnection. Теперь организуем доступ к таблицам и хранимым процедурам. Для работы с таблицами воспользуемся компонентом simpleDataSet, а для доступа к хранимым процедурам — компоненты SQLDataSet И SQLQuery. ПРЕДУПРЕЖДЕНИЕ Если на этапе разработки приложения при попытке извлечь некоторое значение из базы данных (например, имя таблицы) будет выдано сообщение об ошибке, то это означает, что в параметрах подключения (свойство sqlcStaf f .Params) некорректно указаны путь кбазе данных, имя пользователя, пароль или диалект SQL.
Компоненты SimpleDataSet Расположите в модуле данных семь компонентов SimpleDataSet с вкладки db Express палитры компонентов и измените их свойства в соответствии с табл. 7.1. Таблица 7.1. Компоненты SimpleDataSet
Свойство
Значение
Описание
Name
sidsStaff
Для работы со строками представления FullList
sidsDeps
Для работы с таблицей DEPS Для работы с таблицей POSS Для работы с таблицей REGIONS Для работы с таблицей JOBS Для работы с таблицей FAMILY Промежуточный набор данных для работы с таблицей STAFF
sidsPoss sidsRegions s ids Jobs
sidsFamily • Connection
sidsStaffMid sqlcStaff
200
Глава 7. Приложение "Персонал"
Окончание таблицы 7.1
Свойство
Значение
Описание
DataSet . CoimandType
ctQuery
Для sidsStaff — для выбора данных из представлений используется оператор SELECT. Для всех остальных
DataSet . CoromandText
ctTable select * from FullList DEPS
Для sidsDeps
POSS
Для sidsPoss
REGIONS
Для sidsRegions Для s ids Jobs ДЛЯ sidsFamily
-
JOBS FAMILY DeptID
Для sidsStaffMid Порядок сортировки строк для sidsDeps
PosFullName
Для sidsPoss
Zip
Для sidsRegions Для s ids Jobs
STAFF
DataSet . Sort Fie IdNames
ДЛЯ sidsStaff
EmpID, Start Date EmpID, Kin, BirthDate
Для sidsFamily
ID
Для sidsStaffMid
Напоминаем, что компоненты SimpleDataSet используются для организации редактирования данных на стороне клиента. Если не вызвать метод TSimpleDataSet.ApplyUpdates, TO при закрытии этого набора данных все внесенные пользователем изменения будут потеряны. В приложении "Персонал" основным набором данных является sidsStaff, который представляет собой набор строк, сформированный при помощи представления FullList. (объединяет в себе информацию из таблиц STAFF, REGIONS, DEPS И POSS). Компоненту sidsStsffMid соответствует только таблица STAFF, и этот набор данных используется как "фоновый" для организации редактирования таблицы сотрудников. Это обусловлено тем, что представления напрямую не редактируются.
Компоненты для доступа к хранимым процедурам В предыдущей главе в базе данных s t a f f .gdb были определены две хранимые процедуры — GhildDeptsList и DeleteEmp.
Модуль данных
201
• Первая из них выбирает из таблицы DEPS данные о подчиненных подразделениях. Для доступа к ней в приложении будет использоваться компонент SQLQuery. • Вторая хранимая процедура удаляет данные о сотруднике в соответствии с указанным идентификатором. Для доступа к ней будет использоваться компонент SQLDataSet. Оба эти компонента находятся на вкладке dbExpress палитры компонентов.
Компонент sqlqChildDeptsList 1. 2. 3. 4.
Расположите в модуле данных компонент SQLQuery. Присвойте его свойству Name значение sqlqChildDeptsList. Для свойства SQLConnection укажите ссылку на компонент sqlcstaff. Дважды щелкните мышью в инспекторе объектов на поле свойства SQL и введите в редакторе этого свойства следующий SQL-оператор:
select * from C h i l d D e p t s L i s t | : p D e p t l D j
5. Щелкните мышью на кнопке ОК, чтобы сохранить изменения. 6. Затем дважды щелкните мышью в инспекторе объектов на поле свойства Params. В результате откроется редактор параметров запроса. 7. В данном случае в запросе используется только один параметр — pDeptlD. Выделите его в списке, и в инспекторе объектов присвойте свойству DataType значение ftlnteger, а свойству РагагаТуре значение ptInput (это означает, что параметр является входным). 8. Закройте редактор параметров запроса.
Компонент sqldsDeleteEmp 1. Расположите в модуле данных компонент SQLDataSet. 2. Присвойте его свойству Name значение sqldsDeleteEmp. 3. Для свойства SQLConnection укажите ссылку на компонент sqlcStaf f. Компоненты SQLDataSet используются для доступа не только хранимым процедурам, но и к таблицам. Кроме того, они могут использоваться для выполнения SQL-запросов. Характер использования этого компонента определяется свойствами CoromanoVText и CoramandType. 4. Присвойте свойству sqldsDeleteEmp.CommandType значение ctStoredProc, а СВОЙСТВУ sqldsDeleteEmp.CommandText — значение DeleteErap. 5. Теперь дважды щелкните мышью в инспекторе объектов в поле свойства Params. В данном случае список параметров хранимой процедуры будет сформирован автоматически.
202
Глава 7. Приложение "Персонал"
Создание объектов полей Для того чтобы определить свойства отображения полей в сетках данных, необходимо создать соответствующие объекты, производные от класса TField. 1. Для этого используем редактор полей, который открывается после двойного щелчка мышью на компоненте SimpleDataSet. 2. Дважды щелкните мышью на компоненте s i d s s t a f f , чтобы открыть редактор полей. 3- Щелкните в нем правой кнопкой мыши и выполните команду Add All Fields (Добавить все поля) раскрывшегося контекстного меню. В результате в редакторе полей' будет сформирован полный список полей, соответствующих столбцам представления FullList, 4. Создайте аналогичным образом объекты полей для всех остальных компонентов SimpleDataSet. 5. Инициализируйте свойства полей в соответствии с табл. 7.2. Напомним, что для просмотра свойства поля необходимо выделить его в списке редактора полей. Таблица 7.2, Свойства объектов полей
Поле
СВОЙСТВО DisplayLabel
Другие свойства
Компонент sidsstaff LastName Фамилия FirstName Имя Отчество FatherName DeptShortName Подразделение PosShortName Должность Компонент sidsDeps DeptID Dept Full Name
ID
Полное название Dep t Sho r t Name Сокращенное название ParentDeptID ID "родителя" Компонент sidsPoss PosFullName Полное название PosShortName Сокращенное название PosLevel Уровень Компонент sidsRegions Индекс Zip Area Region City
Область Район Город/село
DisplayForniat = 00000 — формат отображения.
Главная форма
203
Окончание таблицы 7.2 Поле
СВОЙСТВО DisplayLabel
Другие свойства
Компонент sidsjobs StartDate
Начало
StopDate
Конец
CurOrg
Тек .орг.
DisplayFormat =
dd/mm/yyyy — формат отображения даты. DisplayWidth = 9 — ширина отображения поля в символах. DisplayFormat = dd/mm/yyyy DisplayWidth = 9
Компонент sidsFamily Kin
Родство
BirthDate
Дата рождения
Di splay r J Format = dd/mm/yyyy DisplayWidth == 9
KinName
Имя
У тех полей, которые указаны в табл. 7.2, свойство visible имеет значение True, у всех остальных — False. Свойство DisplayLabel содержит заголовок соответствующего столбца в сетке данных. ПРЕДУПРЕЖДЕНИЕ Перед компиляцией приложения не забудьте присвоить свойству sqlcStaff. Connected значение False, чтобы отключиться от базы данных.
Главная форма С целью упрощения приложения не будем использовать компонентов MainMenu и imageList. В этом случае "костяк" главной формы будет состоять из компонента ToolBar (панель инструментов) и компонента PageControl (компонент управление вкладками). Оба эти компонента находятся на вкладке Win32 палитры компонентов. 1. Расположите на форме fmMain компонент ToolBar. 2. Присвойте его свойству Name значение tlbMain, а свойствам EdgeBorders и Indent соответственно значения [ebBottom] и 5. Свойство indent определяет отступ слева для компонентов, размещенных на панели инструментов. 3. Расположите на этой панели инструментов компонент TBitBtn с вкладки Additional палитры компонентов. 4. Присвойте его свойству Name значение bbExit, свойству Kind — значение bkciose, а свойству Caption — значение Выход.
204
Глава 7. Приложение "Персонал"
5. Расположите на свободной части формы компонент PageControl 6. Присвойте его свойству Name значение pcMain, а свойству Align — значение alClient. 7. Щелкните на нем правой кнопкой мыши и выполните команду New Page (Добавить вкладку) раскрывшегося контекстного меню. В результате будет создан компонент класса TTabSheet. Такие компоненты, по своей сути, — обычные панели, доступ к которым можно получать при помощи именованных вкладок компонента PageControl. Таким образом, компонент PageControl может содержать несколько вкладок, на которых независимо друг от друга размещаются различные элементы управления. Распределим все элементы управления пользовательского интерфейса приложения "Персонал" между тремя вкладками: Сотрудники, Справочники и Параметры приложения. 8. Для выбора самого компонента PageControl можно щелкнуть мышью на заголовке вкладки, а для выбора подчиненного компонента TabSheet — внутри панели вкладки. 9. Выделите первый созданный компонент TabSheet, присвойте его свойству Name значение tsStaff, а свойству Caption — значение Сотрудники. 10. Аналогичным образом создайте еще две вкладки и присвойте их свойствам Name значения tsDiets и tsParams, а свойствам Caption — значения Справочники И Параметры приложения. 11.Присвойте свойству pcMain.Tabwidth (ширина заголовков вкладок) значение 200. 12. Перед тем, как запустить приложение, создайте обработчики событий fmMain.OnCreate, fmMain.OnActivate И fraMain.OnClose. (Выделить форму fmMain можно либо при помощи раскрывающегося списка инспектора объектов, либо в окне Object TreeView.) Комментарии 'при желании -можете не вводить. procedure
TfmMain.FormCreate(Sender:
TObject);
begin pcMain.Tablndex := 0; //При запуске приложения //активна вкладка "Сотрудники"
end; '
procedure TfraMain. ForinActivate (Sender : TOb j act ] ; begin //Извлекаем данные всех таблиц with dmStaff do begin sidsDeps.Open; sidsPoss.Open;
Главная форма sidsRegions.Open; sidsStaffMid.Open; sidsJobs.Open; sidsFamily-Open; end; end;
205
.
procedure TfroMain.FormClose(Sender: TObject; var Action: TCloseAction); begin //Закрываем все клиентские наборы данных with dinStaff do begin sidsDeps.Close; sidsPoss.Close; sidsStaff.Close; sidsStaffMid.Close,• sidsJobs.Close; • sidsFamily.Close; sqlcStaff.Close; //Отключаемся от базы данных end; end; 13. Сохраните проект и запустите приложение. Главная форма должна выглядеть примерно так, как показано на рис. 7.1. 14. Закройте приложение и вернитесь в конструктор форм.
Сетрднини
Рис. 7.1. Главная форма приложения "Персонал"
206
Глава 7. Приложение "Персонал"
Вначале разработаем функции не первой вкладки Сотрудники, а двух других вкладок — Справочники и Параметры приложения. 15. Прежде, чем продолжить, расположите на форме два DataSource с вкладки Data Access палитры компонентов.
компонента
16. Присвойте их свойствам Name значения dsStaf f и dsDicts. П.Укажите для свойства dsstaff. DataSet ссылку на компонент dmStaff. sidsStaff, а для свойства dsDicts. DataSet — на компонент dmStaff.sidsDepts.
Вкладка Справочники 1. Перейдите в конструкторе форм на вкладку Справочники и расположите на ней элементы управления в соответствии с листингом 7.1, Листинг 7.1. Элементы управления, расположенные на вкладке Справочники object tlbDicts: TToolBar Height = 2 9 ButtonHeight = 23 EdgeBorders = [ebBottom] object cbDict: TComboBox Left = 0 Top = 3 Width = 121 Height = 21 Style = csDropDownList Itemlndex = 0 TabOrder = 0 Items.Strings = ( ' Подразделения' >• 'Должности' 'Регионы') end object paCountRef: TPanel Left = 121 Top = 2 Width = 113 Height = 23 BevelOuter = bvLowered Caption = '0/0' Font.Style = [fsBold] end object dnaDicts: TDBNavigator Left = 234
•
' '
.
Главная форма
207
Окончание листинга 7.1 Гор = 2 Width = 390 Height = 23 DataSource = dsDicts end end object dgrDicts: TDBGrid Align = alClient DataSource = dsDicts end SI Чтобы быстро вставить представленный кол на листинге 7.1 в приложение, откройте с прилагаемого к книге диска файл StfMain.dfm в любом текстовом редакторе, скопируйте его содержимое в буфер обмена Windows (), а затем выполните команду вставки Edit >• Paste () при выбранной вкладке tsDicts,
Внешний вид вкладки Справочники в режиме конструктора должен соответствовать рис. 7.2. Т Flepcotidn
-lOi*
_k 1Л5Ц*Э Сатряп*и
3r
Параметры тмпспмнн
О/О
Рис. 7.2. Вкладка Справочники в конструкторе форм Обратите внимание на то, что в листинге 7.1 компоненты cboict, paCountRef и dnaDicts являются "вложенными" по отношению к компоненту tlbDicts. Это означает, что они размещены в форме на этом компоненте (см. рис. 7.2). Теперь создадим необходимые обработчики событий. 2. Выделите компонент pcMain и создайте для него обработчик события OnCha'nge, который вызывается при переходе с вкладки на вкладку: procedure TfmMain.pcMainChange(Sender: TObject); begin
208
Глава 7. Приложение "Персонал"
//Проверка текущей выбранной вкладки по ее индексу case pcMain.Tablndex of 0: ;
1: with dmStaff do begin sidsDeps.Refresh; //Обновляем содержимое из таблицы DEPS sidsPoss.Refresh; //Обновляем содержимое из таблицы POSS sidsRegions .Refresh,- //Обновляем содержимое //из таблицы REGIONS dgrDicts.SetFocus; //переносим курсор в сетку данных end; end; end;
Управляющая конструкция case используется для выбора одного из нескольких возможных значений целочисленного или символьного свойства или переменной. Программный код, выполняемый при переходе на остальные две вкладки, реализован в разделах "Вкладка Параметры приложения" и "Вкладка Сотрудники" этой главы. >»
Синтаксис программных хонструкций языка Object Pascal рассматривается в приложении А.
3. В конструкторе форм дважды щелкните мышью на компоненте cbDict и добавьте в созданный метод cbDictChange следующий программный код: case cbDict.Itemlndex of //Выбор в списке элемента "Подразделения" О: dsDiets.DataSet := dmStaff.sidsDeps; //Выбор в списке элемента "Должности" 1: dsDicts.DataSet := dinStaff. sidsPoss; //Выбор в списке элемента "Регионы" 2: dsDicts.DataSet := dmStaff.sidsRegions; end;
Здесь компонент dsDicts подключается к набору данных, который соответствует элементу, выбранному в списке cbDict. Компонент dsDict является источником данных для сетки dgrDicts и компонента dnaDicts. 4. Дважды щелкните мышью на компоненте dsDicts. В результате будет создан обработчик события dsDicts.OnDataChange, который вызывается при любых изменениях в текущей записи набора данных. С его помощью организуем индикацию текущей позиции в наборе данных. 5. Вставьте в метод dsDictsDataChange следующий фрагмент кода: paCountRef.Caption := IntToStr(dsDicts.DataSet.RecNo) + '/' + IntToStr(dsDicts.DataSet.RecordCount);
Главная форма
209
Функция intToStr используется для преобразования целочисленных значений в текстовые. Таким образом, текущее положение в наборе данных отображается на панели paCountRef в формате <Текущая запись>/<Всего записейх 6. Сохраните проект и запустите приложение. 7. Перейдите на вкладку Справочники и последовательно выбирайте в раскрывающемся списке различные элементы. Внешний вид сетки данных будет изменяться в соответствии с выбранным справочником (рис. 7.3). S. Закройте приложение и вернитесь в конструктор форм.
Яш
Рис. 7.3. Вкладка Справочники во время выполнения приложения
Редактирование таблиц справочников Теперь необходимо создать несколько обработчиков событий, связанных с редактированием наборов данных simpleDataSet. 1. Во-первых, создадим функцию ApplyOperation, которая будет вызываться после каждой операции в наборе данных с целью передачи внесенных изменений на сервер. Объявите в интерфейсной части модуля stfDMod.pas функцию ApplyOperation: type TdrnStaff = class (TDataModule) function ApplyOperation(sids:
TSimpleDataSet): integer;
private
2. Создайте реализацию этой функции в разделе implementation:
210
Глава 7. Приложение "Персонал"
function TdmStaff.flpplyOperation(sids: TSimpleDataSet): integer; begin with sids do begin Result := ApplyUpdates(0); //Возвращается количество //ошибок согласования Refresh; //Обновление набора данных после //внесения изменений на сервере end; end;
В функцию в качестве параметра передается ссылка на компонент типа TSimpleDataSet, а возвращается количество ошибок согласования, возникших при передаче обновлений на сервере после вызова метода TSimpleDataSet .ApplyUpdates. После передачи изменений на сервер выполняется обновление содержимого клиентского набора данных при помощи метода Refresh.
Обработчики событий набора данных sidsDeps Набор данных sidsDeps, соответствующий таблице DEPS, имеет одну особенность. Содержимое поля Parents должно формироваться автоматически на основании значения в поле ParentDeptlD. Это означает, что при внесении изменений в текущую запись необходимо формировать перечень идентификаторов "родительских" подразделений в соответствии с иерархической структурой. Для этого воспользуемся обработчиком события sidsDeps.OnAfterpost, который вызывается после сохранения внесенных в запись изменений. Кроме того, в обработчике события sidsDeps.AfterEdit, который вызывается при внесении изменений в запись, следует очищать содержимое поля Parents от прежнего значения. 1. Выберите в модуле данных компонент sidsDeps. 2. Создайте при помощи инспектора объектов обработчик события OnAfterEdit и вставьте в него следующий оператор: sidsDepsParents.Clear; 3. Создайте обработчик события OnAfterPost (комментарии при желании можете не вводить): procedure T d m S t a f f . s i d s D e p s A f t e r P o s t ( D a t a S e t : TDataSet); var Parents: string[100]; //Строка идентификаторов //"родительских" подразделений CurDeptID: integer; //Идентификатор текущего подразделения begin if ApplyOperation(sidsDeps) = 0 then //Сохраняем внесенные
begin
•
//изменения в базе данных
Главная форма
211
fmMain.dgrDicts.Visible := False; //Временно скрываем //сетку данных with sidsDeps do begin Parents := ''; First; while not EOF do . begin //Находим подразделение, которое ... II... не расположено на вершине иерархии и ... if (sidsDepsParentDeptlD.Aslnteger > 0) and (sidsDepsParents.AsString = '') //... не имеет //списка "родителей" then begin //Запоминаем текущую позицию CurDeptID := sidsDepsDeptID.Aslnteger; while sidsDepsParentDeptlD.Aslnteger > 0 do begin //До тех пор, пока не достигнем вершины иерархии... //...добавляем к списку Parents //идентификатор текущего "родителя" Parents := IntToStr(sidsDepsParentDeptlD.Aslnteger) + 1 ,' + Parents; //...переходим к подразделению, //которое для текущего является "родительским" Locate('DeptID',sidsDepsParentDeptlD.Aslnteger,[]); end; break; //Список Parents сформирован //прерываем просмотр набора данных end; Next; end; if Parents <> '' then begin //Если подразделение не расположено на вершине иерархии Locate('DeptID',CurDeptID,[]); //Переходим к соответствую//щему подразделению //Вносим с поле Parents список "родителей" Edit; sidsDepsParents.AaString := Parents; Post; end; end;
212 Глава 7. Приложение "Персонал" //Отображаем сетку данных fmMain.dgrDiats.Visible := true; fmMain.dgrDiats.SetFocus; end else MessageDlgf'Ошибка выполнения операции!', mtError,[mbok],0); end;
Обратите внимание на метод Locate. Он используется в наборах данных для поиска записей по ключевым значениям. В базовом варианте в него передается три параметра: список полей, по которым осуществляется поиск, массив соответствующих значений поиска и набор параметров поиска. В методе sidsDepsAfterPost при сохранении информации о "родительских" подразделениях вызывается метод sidsDeps.Post, который в свою очередь приводит к возникновению события OnAfterPost. Подобные вызовы методов самих себя называются рекурсивными. Однако при повторном вызове метода sidsDepsAfterPost уже не будет существовать записей, для которых необходимо определить содержимое поля Parents. По этой причине первый цикл while...do просто просмотрит все строки набора данных, не выполнив никаких операций. Во избежание мелькания информации на экране при просмотре набора данных сетка данных временно скрывается. 4. Теперь создайте обработчик события sidsDeps.OnAfterDelete, который вызывается после удаления строки из набора данных. Вставьте в него следующий оператор: if ApplyOperation(sidsDeps) > 0 then MessageDlgl'Операция некорректна!',mtError,[mbok],0);
Если есть ошибки согласования при удалении записи таблицы DEPS, например, эта запись уже была удалена другим пользователем базы данных, то на экран выводится соответствующее сообщение. Последнее событие, которое необходимо обработать, это sidsDeps.OnReconcileError. Оно происходит при обнаружении ошибок согласования данных в результате вызова метода ApplyUpdates. 5. Создайте обработчик события sidsDeps.OnReconcileError и вставьте в него следующую строку: MessageDig(E.Message,mtError,[mbok],0);
Параметр Е — это объект класса EReconcileError, в котором определено свойство Message. Это свойство, содержащее информацию о типе ошибки, используется для формирования пользовательского сообщения.
Обработчики событий набора данных sidsPoss В наборе данных sidsPoss идентификатор должности вручную не вводится, как в случае с подразделениями. Однако в поле идентификатора PosID обязательно должно быть занесено некоторое целочисленное значение, для чего луч-
Главная форма
213
ше всего использовать обработчик события sidsPoss .OnBeforePost. Этот обработчик вызывается в момент перед сохранением изменений, внесенных в запись набора данных. 1. Создайте обработчик события sidsPoss.OnBeforePost и вставьте в него следующую строку: sidsPossPosID.AsInteger := 1;
Здесь в поле POSID заносится единица, но это могло быть и любое другое целое число, В данном случае это не имеет значения, так как фактически значение поля posID в таблице POSS определяется сервером базы данных автоматически при помощи генератора GEN_POS_ID. 2. Теперь создайте обработчик события sidsPoss.OnAfterDelete и вставьте в него следующую строку программного кода: ApplyOperation(sidsPoss;
3. В инспекторе объектов для события sidsPoss.OnAfterPost укажите в качестве обработчика метод siolsPossAfterDelete. 4. Аналогичным образом укажите для события sidsPoss .OnReconcileError в качестве обработчика метод sidsDepsOnReccmcileError.
Обработчики событий набора данных sidsRegions 1. Создайте обработчик события sidsRegions.OnAfterDelete и вставьте в него строку: ApplyOperation(sidsRegionsj ;
2. Так же как и для набора данных sidsPoss, в инспекторе объектов укажите для события sidsRegions.OnAfterPost в качестве обработчика метод sidsRegionsAfterDelete. 3. Аналогичным образом укажите для события sidsRegions.OnReconcileError в качестве обработчика метод sidsDepsOnReconcileError. 4. Взаимодействие с наборами данных sidsJobs, sidsFamily и sidsStaffMid рассмотрено в разделах "Процедура DataForEmployee", "Автоматическая вставка данных в таблицу JOBS" и "Работа с таблицами FAMILY и JOBS" этой главы. На данном этапе достаточно назначить для них в качестве обработчика события OnReconcileError метод sidsDepsOnReconcileError. 5. Прежде, чем откомпилировать приложение, укажите в разделе implementation модуля StfDMod.pas ссылку на модуль StfMain.pas: implementation uses StfMain;
Это необходимо сделать для того, чтобы из модуля StfDMod можно было обращаться к элементам формы fmMain. 6. Теперь можно сохранить проект и запустить приложение.
214
Глава 7. Приложение "Персонал"
Добавьте в таблицы DEPS, POSS и REGIONS несколько записей, например, соответствующих штату вашей организации. 7. Для примера в созданное приложение были введены следующие данные. • На вершину иерархии (поле ParentDeptlD = о) бьши добавлены два подразделения: Работающие и Уволенные, а в подразделение Работающие — подчиненное подразделение Руководство (поле ParentDeptlD содержит идентификатор подразделения Работающие), как показано на рис. 7.4.
.-jgj-xj
7/ Персонал
Сгракякжи
313
[Псорамеления IO
Параметры гфиловния
] Псиное назван»
j РжарЩ&МР
*1
Работающие УйОла**ч румосете
-
Рис. 7.4. Содержимое таблицы DEPS
Также было создано четыре "должности": Начальник (уровень 1), Заместитель начальника (уровень 2), Начальник участка (уровень 3) и Рабочий (уровень 4) , как показано на рис. 7.5.
Справочники
>4/4 Лолмм Занесгья*гь начальника Чачаяьмип
и I - I "
Пччмгтры E4»H"Qi«t*ia
* I -U I "[Сокращенно название [уровень Зам началывка Начальник Нач участка (рдбочй
Рис. 7.5. Содержимое таблицы POSS
| _£J
Главная форма 8.
215
В таблицу REGIONS можно ввести данные, соответствующие месту проживания сотрудников.
9. Закройте приложение и перейдите в конструктор форм.
Вкладка Параметры приложения 1. В конструкторе форм перейдите на вкладку Параметры приложения и расположите на ней элементы управления в соответствии с листингом 7.2. Листинг 7.2. Элементы управления, расположенные на вкладке Параметры приложения object valeParams: TValueListEditor Height =194 Align = alTop DefaultColWidth = 200 DisplayOptions = [doAutoColResize, doKeyColFixed] Strings.Strings = ( 'База данных=' ' Пользователь^' 'Организация=' 'Код организации^1 'Начальник организации (должностьJ=' 'Начальник организации (Ф.И.О.)=') end object btDefaulta: TButton Left = 16 Top =204 Width =117 Height = 2 5 Caption = 'Восстановить' . end object btSave: TButton Left = 144 Top =204 Width = 117 Height = 25 Caption-= 'Сохранить1 Enabled = False end Внешний вид этой вкладки представлен на рис. 7.6. Обратите внимание на то, что кнопка Сохранить пока недоступна. Конечно же, список параметров может быть и больше, но ограничимся перечисленными шестью параметрами.
216
Глава 7. Приложение "Персонал"
• Параметры грнюжония
Сгравоинмы! База ценных Пользователь Организация
....
Начальник оргашзацим (должность) Начаяь** ссганиэацни [Ф И.О J
Рис. 7.6. Вкладка Параметры приложения 2. Прежде, чем создать обработчики событий для элементов управления вкладки Параметры приложения, откройте в конструкторе форму fmLogin и дважды щелкните мышью на кнопке bbConnect. 3. В метод bbConnectClick внесите изменения, в соответствии с представленным ниже фрагментом программного кода: procedure TfmLogin.bbConnectClick(Sender: TObject); var Reg: TRegistry;
begin Screen.Cursor := crDefault; Reg := TRegistry.Create; if not Reg.KeyExists('Software\Staff') then begin WriteStrParam('Организация','Наша организация'); WriteStrParam('Код организации',''); WriteStrParam('Начальник организации (должность)',11) WriteStrParam('Начальник организации (Ф.И.О.) ',''); end;
Reg.Free; WriteStrParamf'База данных',laedDatabase.Text); WriteStrParam{'Пользователь',laedUser.Text); 4. Для того чтобы использовать класс TRegistry, укажите в разделе uses интерфейсной части модуля stfLogin.pas ссылку на модуль Registry. Во фрагменте, выделенном полужирным шрифтом, выполняется проверка наличия в системном реестре раздела s t a f f . Если его нет, то есть это первый запуск приложения, то раздел Staff создается, а в нем создается набор пара-
Главная форма
217
метров с некоторыми начальными значениями. В отличие от параметров База данных и Пользователь, которые при запуске приложения могут обновляться, остальные параметры изменяются только при помощи вкладки Параметры приложения на форме fmMain. 5. Перейдите в конструкторе к форме fmMain. 6. Найдите в исходном коде обработчик события pcMain.OnChange и внесите в него следующие изменения: procedure TfmMain.pcMainChange(Sender: TObject); begin case pcMain.Tablndex of .
2: with valeParams do 'begin Col := 1; //активизируем второй столбец SetFocus; end; end; end;
7. Создайте в разделе private класса TfmMain (модуль stfMain.pas) объявление Процедуры LoadParams: private { Private declarations ( procedure LoadParams;
Зга процедура будет использоваться для загрузки параметров приложения из системного реестра в компонент valeParams. 8. Создайте реализацию процедуры LoadParams в разделе implementation: procedure ТfmMain.LoadParams; var i: integer; begin with valeParams do begin Itemprops[0]-EditStyle := esEllipsis; //В поле значения //отображается кнопка for i := 0 to Strings.Count - 1 do //считываем значения параметров по их именам Values[Keys[i]] := ReadStrparam(Keys[i]); end; end;
Свойство TValueListEditor.ltemProps содержит наборы свойств для каждого из элементов списка параметров. В частности, при помощи свойства
218
Глава 7. Приложение "Персонал"
Editstyle можно определить стиль редактирования каждого параметра. Это свойство может принимать три значения: essimple (по умолчанию), esEllipsis (кнопка), esPickList (раскрывающийся список), В данном случае для первого параметра (База данных) выбран стиль esEllipsis. Это означает, что в поле отображается маленькая кнопка, с помощью которой можно производить некоторые действия. В разрабатываемом приложении при помощи этой кнопки будет раскрываться окно поиска файла. Свойство TValueListEditor.Keys — это набор всех строковых элементов первого столбца, а свойство TValueListEditor.values — это набор всех элементов второго столбца. 9. Создайте обработчик события valeParams. OnEditButtonClick, которое возникает в момент щелчка мышью на кнопке, расположенной в поле первого параметра: procedure
TfmMain.valePararasEditButtonClick(Sender:
TObject);
begin with dmStaff.od_l, valeParams do begin Filter := 'База данных I n t e r B a s e | * . g d b ' ; FileName := C e l l s [ C o l , R o w ] ; if Execute then begin Cells[Col,Row] := FileName; btSave.Enabled := True; end;
end; end;
Свойство valeParams.Cells — это двухмерный массив, соответствующий содержимому всех ячеек компонента. Свойство valeParams.Col содержит номер текущего столбца, а свойство valeParams.Row — номер текущей строки. Обратите внимание на то, что при изменении значения параметра База данных кнопка btSave становится доступной. Эта кнопка должна также становиться доступной при изменении значений остальных параметров. 10. Создайте обработчик события valeParams.OnKeyPress, который вызывается при нажатии пользователем на клавишу клавиатуры, и вставьте в него только одну строку: btSave.Enabled := True;
Н.Создайте обработчик события btDefsuits.Onclick и вставьте в него следующие строки;
Главная форма
219
LoadParams; btSave.Enabled := False;
12.Также создайте обработчик события btSave.OnClick: procedure TfmMain.btSaveClick(Sender: T O b j e c t ) ; var i: begin
integer;
with valeParams do f o r i := 0 to Strings.Count - 1 do WriteStrParam(Keys[i],Values[Keys[i]]) ; btSave.Enabled := False; end;
Таким образом, если внести изменения в значения параметров, а затем нажать кнопку Восстановить до того, как была нажата кнопка Сохранить, то будут загружены исходные значения из системного реестра. 13. Осталось добавить вызов процедуры LoadParams в обработчик события fmMain.OnActivate: procedure TfmMain.FormActivate(Sender: TObject}; begin LoadParams; end;
14. Сохраните проект, запустите приложение. 15. Введите новые значения параметров и щелкните мышью на кнопке Сохранить 16. Закройте приложение и перейдите в конструктор форм.
Вкладка Сотрудники Перейдем к разработке основной части приложения "Персонал". Соответствующие элементы пользовательского интерфейса должны быть размещены на вкладке Сотрудники. В конструкторе форм перейдите на вкладку Сотрудники и расположите на ней элементы управления в соответствии с листингом 7.3. Листинг 7.3. Элементы управления, расположенные на вкладке Сотрудники object slMain: TSplitter Align = alLeft end
Продолжение листинга 7.3 object paStaff: TPanel Align = alClient BevelOuter = bvLowered Visible = False object paSearch: TPanel Height = 50 Align = alTop object laSearch: TLabel Left = 128 Top = 4 Caption = 'Поиск:' end object paCount: TPanel Left = 8 Top = 8 Width = 1 1 3 Height = 3 3 BevelOuter = bvLowered Caption = '0/0' Font.Style = [fsBold] end object ckByPoss: TCheckBox Left = 392 Top = 24 Width = 173 Caption = 'Сортировать по должностям1 end object cbSearch: TComboBox Left = 128 Top = 2 0 Width = 1 2 9 Style = csDropDownList Iteralndex = 0 Items.Strings = ( 1 по фамилии' 'по идентифик. коду' 'по табельному ff' 'по телефону' 'по номеру паспорта') end object edSearch: Tedit
Главная форма Продолжение листинга 7.3 Left = 260 Тор = 20
Width - 105 end end object dgrStaff: TDBGrid Height = 180 Align = alTop DataSource = dsStaff Options = [dgTitles, dgColumnResize, dgColLines, dgRowLines, dgTabs, dgRowSelect, dgAlwaysShowSelection, dgCancelOnExit] Readonly = True end object tlbStaffList: TToolBar Height = 2 6 AutoSize = True object dnaStaffLiat: TDBNavigator Left = 0 Top = 2 Width = 224 Height = 2 2 DataSource = dsStaff VisibleButtons = [nbFirst, nbPrior, nbNext, nbLast] end object btHewEmp; TButton Left = 224 Top - 2 Width = 8 5 Height = 22 Caption = 'Новый' end object btEmpData: TButton Left = 309 Top = 2 Width = 84 Height = 22 Caption = 'Данные' end. object btDeleteEmp: TButton Left = 393
221
222
Глава 7. Приложение "Персонал"
Окончание листинга 7.3 Top = 2 Width = 8 4 Height = 2 2 Caption = ' У д а л и т ь ' end end object paData: TPanel Align = alClient object sgData: TStringGrid
object dimPhoto: TDBImage Align = alClient Color = clScrollBar DataField = 'PHOTO' DataSource = d s S t a f f end end end
Теперь вкладка Сотрудники должна выглядеть примерно так, как показано на рис. 7.7. Для начала рассмотрим компонент tvstructure, расположенный вдоль левого края вкладки Сотрудники.
Отображение структуры организации при помощи компонента TreeView Компонент tvStructure — это объект класса TTreeView, который используется для работы с иерархическими структурами. Основным свойством объектов класса TTreeView является свойство Items. Оно представляет собой массив объектов класса TTreeNode. У этих объектов есть несколько основных свойств, некоторые из которых используются и в приложении "Персонал". • Aosoiuteindex — порядковый номер элемента в иерархии (начиная с 0). • Count — количество подчиненных элементов.
Главная форма
223
Параметры приложния
ОЛ)
Пиит*1 Сорпч»мгь подогамостям
Рис. 7.7. Вкладка Сотрудники
•
index — порядковый номер элемента среди элементов, имеющих одного и того же "родителя". • Level — уровень подчиненности, на котором находится элемент. • Parent — объект класса TTreeNode, соответствующий "родительскому" элементу. * • Text — строка, соответствующая элементу в иерархии. Создать иерархию элементов, отображаемых компонентом TreeView, можно программно, или при помощи редактора свойства items. Для вызова редактора свойства items можно воспользоваться одним из следующих способов. • Дважды щелкнуть мышью на компоненте TreeView в конструкторе форм. • Дважды щелкнуть мышью в инспекторе объектов в поле свойства items. Каждое из этих действий приводит к раскрытию на экране диалогового окна, представленного на рис. 7.8. Рассмотрим элементы управления окна TreeView Items Editor. • Кнопка New Item создает новый элемент в корневом уровне иерархии.
224
Глава 7. Приложение "Персонал"
Рис. 7.8. Редактор свойства TTreeView. Items
. Кнопка New Subitem создает новый подчиненный элемент для элемента, выбранного в данный момент. Кнопка Delete удаляет выбранный в данный момент элемент. Кнопка Load загружает иерархию из внешнего файла. Поле Text возвращает текст, соответствующий выбранному в данный момент элементу. Поле Imagelndex возвращает номер изображения, хранимого в компоненте ImageList, если ссылка на такой компонент • указана в свойстве TreeView.ImageList. Поле Selected Index возвращает номер изображения, хранимого в компоненте ImageList, которое используется для обозначения текущего элемента. Поле State Index возвращает номер изображения, хранимого в компоненте ImageList, которое используется в качестве дополнительной пиктограммы "состояния" элемента. i Создайте в редакторе свойства tvStructure. Items один элемент, который будет использоваться в приложения "Персонал" в качестве корневого. Для этого щелкните мышью на кнопке Hew Item и введите в поле Text строку Полный список. После этого щелкните на кнопке Apply, если хотите продолжить работу в редакторе свойства items, или на кнопке ОК, если хотите вернуться в конструктор форм. Компонент tvStructure должен отобразить только что созданный корневой элемент иерархии. Создайте обработчики для двух событий компонента tvStructure: OnChange — наступает при изменении выбранного элемента в иерархии, и OnClick — наступает при щелчке мышью на компоненте: procedure TfmMain.tvStructureChange(Sender: Node: begin paStaff.Visible := True; end;
TObject;
TTreeNode);
Главная форма
225
procedure TfmMain.tvStructureClickfSender: TObject); begin dgrStaff.SetFoous; end;
В первом методе отображается панель paStaf f, содержащая элементы управления, предназначенные для работы с набором данных sidsStaff. Во втором методе курсор переносится в сетку данных dgrStaf f.
Структура организации — процедура LoadStructure Создадим процедуру LoadStructure, в которой будет выполняться создание иерархической структуры подразделений на основании информации, извлекаемой из таблицы DEPS. При этом ключевую роль будут играть поля DeptFullName — название подразделения, отображаемое в иерархии, и ParentDeptlD — идентификатор "родительского" подразделения. Для формирования списка подчиненных подразделений будет использована хранимая процедура ChildDeptsList. 1. Объявите в модуле StfHain.pas процедуру LoadStructure как открытый член класса TfmMain: type TfmMain = class (TForrn)
procedure Loadstructure; private 2. В разделе implementation создайте реализацию этой процедуры (комментарии при желании можете не вводить): procedure TfmMain,LoadStructure; var CurElement, CurParentID: integer; begin CurElement := 0; //Номер текущего элемента в иерархии with'tvStructure, dmStaff do begin Items[0]. DeleteCh-ildren; //удаляем все элементы иерархии, //кроме корневого repeat ,. //если текущим является корневой элемент... if CurElement = 0 then CurParentID := 0 //...то ID //"родителя" = О else begin II... в противном случае ищем в таблице DEPS запись.
226
,
Глава 7. Приложение "Персонал"
//соответствующую текущему элементу..sidsDeps.Locate('DeptFullName1,Items[CurElement].Text, []); //.. .и извлекаем идентификатор //"родительского" подразделения CurParentlD := sidsDeps.FieldByName('DeptID').Aslnteger; end; with sqlqChildDeptsList do begin //Выбираем из таблицы DEPS список //подчиненных подразделений Close; ParamByNameCpDeptID') .Aslnteger := CurParentID; Open; //Просматриваем этот список ... while not EOF do begin / / . . . и добавляем к текущему элементу //иерархии соответствующие подчиненные элементы Iterns.AddChildflterns[CurElement], FieldByName C r D e p t F u l l N a m e ' ) . A s S t r i n g ) ;
Next; end; end; incfCurElement]; //Увеличиваем номер текущего элемента на 1 until CurSlement = (Items.Count - 1); //Повторяем цикл до тех //лор, пока не будет достигнут //последний элемент иерархии Items.Item[0].Expand(True); //разворачиваем корневой элемент end; end;
Алгоритм, который использовался в этой процедуре, выглядит примерно так . Переменная CurElement хранит номер текущего элемента иерархии. При первом проходе цикла г ере at...until (см. подробнее об этом цикле ниже в этом разделе) CurElement = 0 (номер корневого элемента). Для всех элементов, непосредственно подчиненных корневому, в поле ParentDeptlD хранится нуль, поэтому переменной CurParentID для корневого элемента также присваивается нуль. Далее при помощи компонента sqlqChildDeptsList, который выполняет хранимую процедуру childDeptsList, из таблицы DEPS извлекается перечень подразделений, подчиненных текущему подразделению. В данном случае это будут подразделения работающие и Уволенные, которые и добавляются к корневому элементу Полный список.
Главная форма
227
• Затем счетчик CurElement увеличивается на единицу при помощи процедуры inc, и в данном случае указывает на элемент Работающие. Цикл repeatuntil повторяется сначала, так как последний элемент иерархии еще не достигнут. • Потом в таблице DEPS при помощи метода Locate выполняется поиск записи, соответствующей названию подразделения Работающие, и переменной Curparentio присваивается значение поля ID, хранимое в найденной строке таблицы. • После этого выбираются и заносятся в иерархию подразделения, подчиненные подразделению Работающие. В данном случае это будет подразделение Руководство. • Затем этот цикл повторяется для подразделений Руководство и Уво!ленные. НЕМНОГО ТЕОРИИ Циклы repeat...until отличаются от циклов while...do тем, что выход из них осуществляется в том случае, когда условие цикла имеет значение True. Из циклов же while,..do выход осуществляется в том случае, когда условие цикла имеет значение False. Кроме того, операторы, помещенные между ключевыми словами repeat и until, выполняются как минимум один раз, в то время как цикл while...do может не выполнить ни одного прохода. •
Обратите внимание на использование компонента sqlqChildDeptsList. Для инициализации параметра pDeptlD, передаваемого на сервер, использовался метод ParamByName. Перед применением этого метода набор данных, возвращенный хранимой процедурой childDeptsList в результате выполнения предыдущего запроса, должен быть закрыт при помощи метода SQLQuery,Close. • Достижение конца набора записей проверяется при помощи функции EOF (чтобы проверить достигнуто ли начало набора записей, используется функция BOF). Эти функции используются всеми компонентами, предназначенными для работы с наборами данных.
После того, как была создана процедура формирования иерархической структуры организации, необходимо вставить ее вызовы в различные методы обработки событий. 3. Первый обработчик события, вызывающий процедуру LoadStructure, — это, конечно же, метод PormActivate формы fmMain: procedure TfmMain.Formftctivate(Sender: T O b j e c t ) ; begin LoadStructure;
end;
228
Глава 7. Приложение "Персонал"
4. Кроме того, структуру подразделений необходимо обновлять в том случае, если были внесены какие-либо изменения в таблицу DEPS. Для того чтобы отслеживать эти изменения, объявите в модуле stfDMod.pas глобальную переменную DepsChanged типа boolean (переменные этого типа принимают только два значения — True и False): var d m S t a f f : TdmStaff; DepsChanged: boolean;
НЕМНОГО ТЕОРИИ Глобальными называются переменные, видимые не только в любой точке программного модуля, в котором они объявлены, но и в других модулях. Такие переменные объявляются при помощи оператора var в интерфейсной части программного модуля. Аналогами глобальных переменных являются открытые члены классов (public или published). Существуют также локальные переменные, видимые только в пределах того модуля или той процедуры, где они объявлены (аналоги закрытых членов класса). Например, переменные CurElement и CurParentID являются локальными, видимыми только в пределах процедуры Loadstructure. 5. Внесите необходимые изменения в следующие обработчики событий: procedure TdraStaff.sidsDepsAfterDelete(DataSet: begin DepsChanged := True;
TDataSet);
end;
procedure TdmStaff.sidsDepsAfterPost(DataSet: TDataSet); var Parents: string[100]; CurDeptID: integer; begin DepsChanged r= True; end;
В первом случае флажок DepsChanged устанавливается после удаления записи из набора данных sidsDeps, а во втором случае — после внесения изменений в данные. 6. Теперь реализуем обновление информации о структуре организации в тот момент, когда пользователь перехолит на вкладку Сотрудники (событие pcMain.OnChange):
Главная форма
229
procedure TfmMain.pcMainChange(Sender: TObject); var Curltemldx: integer; begin //Проверка текущей выбранной вкладки по ее номеру case pcMain.Tablndex of 0: if DapsChanged then //Если в таблицу DEPS //были внесены изменения with tvStructure do begin //Запоминаем порядковый номер текущего подразделения Curltemldx := Selected.Absalutelndex,• Loadstruoture; //Обновляем структуру организации //Если было выбрано последнее подразделение в иерархии, //и его больше не существует, ... if Curltemldx > (Items.Count - 1) then Select(Items[Items.Count-l]) //тогда выбираем //последнее подразделение в обновленной структуре else Select(Items[CurItemIdx]); //иначе выбор //не изменяется if paStaff.Visible then dgrStaff.SetFocus else SetFocua; end; 1: with dmStaff do begin DepsChanged := False;
end; end; end; Переменная Curltemldx используется для запоминания порядкового номера текущего подразделения в иерархии до обновления структуры организации. Если было выбрано последнее подразделение в иерархии, которое после обновления структуры оказывается удаленным, то текущим становится последнее подразделение в обновленной иерархии. Оператор if paStaff.Visible then dgrStaff.SetFocus else SetFocus; можно прочитать следующим образом: "Если при переходе на вкладку Сотрудники панель paStaff видима, тогда курсор переносится в сетку данных dgrStaff, в противном случае курсор переносится на компонент tvStructure". При переходе на вкладку Справочники флажку DepsChanged присваивается значение False, что означает "изменений в таблице DEPS нет".
230
Глава 7. Приложение "Персонал"
7. Сохраните проект и запустите приложение. Теперь компонент tvStructure должен отображать иерархическую структуру организации. 8. Перейдите на вкладку Справочники и создайте подразделение Отдел кадров, подчиненное подразделению Работающие, как показано на рис. 7.9. 17т' Персонал
-iDlx!
jft Вы™
_
Опрдомш Поарашелвня ID
s
л
1 2 3 4
_^][
LnpABO4*m 4/4
П олное нззв**е Работающие Уволенные Руководство Отдел к аиров
Ч \
•* \
1
Параметры фчло*ы«
>• | W j + )
—
1
е
* 1 iLj 1 i
f Сскадд** назвало "рсиигегн"! *] Работаощне 0 Уеопомт 0 Р»лваиТВ 1 ОК
Рис. 7.9. Новое подразделение Отдел кадров 9. Перейдите на вкладку Сотрудники. Компонент tvstructure должен отображать обновленную структуру организации, как показано на рис. 7.10.
Выбор данных о сотрудниках — процедура SetSelection Выбор данных о сотрудниках из представления FullList выполняется на основании текущего выбранного элемента в иерархии tvstructure, a также состояния флажка Сортировать по должностям (компонент ckByPoss). Для этого создадим отдельную процедуру. 1, Объявите в модуле StfMain.pas процедуру SetSelection как закрытый член класса TfmMain: •••
type TfmMain = class(TForm)
-• Полый c - Работающие Ручводсгво Отдел кадров Уволенные
Рис. 7.10. Компонент
tvstructure отображает обновленную структуру организации
2. В разделе implementation создайте реализацию процедуры SetSelection (комментарии при желании можете не вводить):
Главная форма
231
procedure TfmMain.SetSelection; begin Screen.Cursor := crSQLWait; //Изменяем форму указателя мыши //на "песочные часы" with dmStaff, sidsStaff, tvStructure.Selected do begin Close; //Закрываем набор данных sidsStaff //Выбираем все столбцы из представления FullList DataSet. ComrnandText := 'select * from FullList '; if Absolutelndex > 0 then begin //Если выбран НЕ корневой элемент структуры, то ... //Находим соответствующую запись в таблице DEPS sidsDeps.Locate('DeptFullName', Text,[]); //Добавляем условие в запрос //выбор сотрудников из всех подразделений, для которых //содержимое поля Parents начинается с соответствующего //списка идентификаторов DataSet .CommandText := DataSet. CommandText + 'where DepID = ' 4- IntToStr(sidsDepsDeptlD.ftsInteger) + 1 or Parents starting with ' ' '; if Parent.Absolutelndex > 0 then DataSet.CommandText := DataSet,CommandText + sidsDepsParents.AsString; //Добавляем к списку Parents идентификатор //текущего подразделения DataSet.CommandText := DataSet.CommandText + IntToStr(sidsDepsDeptlD.AsInteger) + i 'IP. I
Г
end; if ckByPoss.Checked then //Если есть сортировка по уровню должностей, то... DataSet.CommandText := DataSet.CommandText + 'order by PosLevel, LastName, Firstname, FatherName' else //в противном случае сортируем строки по именам DataSet.CommandText := DataSet.CommandText + 'order by LastMame, Firstname, FatherName',Open; //Выбираем данные из представления FullList Screen.Cursor := crDefault; //Восстанвливаем указатель мыши, //выбранный по умолчанию end; end;
232
Глава?. Приложение "Персонал"
Свойство sidsStaff .DataSet. ComrnandType имеет значение ctQuery. В этом случае свойство DataSet. CommandText используется для хранения SQLоператоров запроса. Если, например, в компоненте tvStructure было выбрано подразделение Работающие, и при этом установлен флажок ckByPoss, то свойство sidsStaff .DataSet.CommandText при выполнении запроса будет иметь следующее значение (переносы на новую строку добавлены для удобства чтения): select * from FullList
where DepID = 1 or Parents starting with '!,' order by PosLevel, LastName, Firstname, FatherName Если при том же состоянии флажка ckByPoss выбрать подразделение Руководство, то свойство sidsStaff .DataSet. CommandText будет иметь следующее значение: select * from FullList
where DepID = 3 or Parents starting with '1,3, ' order by Posbevel,
LastHame,
Firstname,
FatherName
Если в компоненте tvStructure выбрать корневой элемент (Полный список) при снятом флажке ckByPoss, то SQL-запрос будет выглядеть следующим образом: ^^ select * from FullList order_by LastHame, Firstname,
FatherHame
Процедура SetSelection должна вызываться при выборе нового элемента в компоненте tvStructure (событие tvStructure.OnChange), а также при изменении состояния флажка ckByPoss (событие ckByPoss.Onclick): procedure TfmMain.tvStructureChange(Sender: Node); begin paStaff.Visible SetSelection; end;
TObject;
Node:
TTree-
:= True,-
procedure T f m M a i n . c k B y P o s s C l i c k ( S e n d e r : begin SetSelection; end;
TObject);
3. Сохраните проект, и запустить приложение. 4. Выберите в структуре какое-либо подразделение. Ничего страшного, что в возвращенном наборе данных нет ни одной строки, — главное, что все работает! 5. Закройте приложение и вернитесь в конструктор форм.
Главная форма
233
Контекстные меню и события сетки данных dgrStaff Для ускорения выполнения операций в приложениях часто используются так называемые контекстные (popup) меню, которые открываются в результате щелчка правой кнопкой мыши на элементе интерфейса. В Delphi контекстные меню создаются при помощи компонента PopupMenu, расположенного на вкладке Standard палитры компонентов. У любого визуального компонента есть свойство PopupMenu, для которого можно указать ссылку на любой компонент контекстного меню. 1. Расположите на форме froMain два компонента PopupMenu. 2. Первому из них присвойте имя puStaf f, а второму — puData. 3. Создайте в этих контекстных меню команды в соответствии с табл. 7.3. Процесс разработки контекстных меню аналогичен процессу разработки обычных меню. « см. подробнее з разделе "Меню и панель инструментов формы fmMain ".главы 2.
Таблица 7.3. Контекстные меню puStaff и puData
Название команды меню
Значение свойства caption
КомпонентpuStaff pmiNewEmp
Новый
pmiEmpData
Данные
pmiDeleteEmp pmil pmiStaf f Cancel
Удалить
Отменить
Компонент puData pmi CommonDat a
Общие
pmi Addit Data
Дополнительные
pmi 2 pmiDataCancel
Отменить
Контекстное меню puStaff дублирует кнопки Новый, Данные и Удалить. Контекстное меню puData должно открываться при щелчке мышью на кнопке Данные или при выполнении команды Данные контекстного меню puStaf f. 4. Назначьте компонент puStaf f в качестве контекстного меню для компонента dgrStaff (свойство PopupMenu). 5. Теперь создадим вызовы контекстного меню puData. Вначале сделаем это в . обработчике события OnClick кнопки btEmpData: procedure TfmMain.btEmpDataClick(Sender : T O b j e c t ) ; begin puData.Popup(btEmpData.ClientOrigin.X,btEmpData.ClientOrigin.Y) end;
234
Глава 7. Приложение "Персонал"
Для принудительного отображения контекстного меню используется метод Popup, в который в качестве двух параметров передаются экранные координаты смещение в пикселях от левого верхнего угла экрана. В данном случае для определения координат отображения контекстного меню используется свойство TButton.ClientOrigin. Оно содержит экранные координаты компонента, в то время как свойства Left и Тор содержат координаты компонента относительно той панели или формы, на которой он расположен. Контекстное меню puData также должно открываться по двойному щелчку мышью на компоненте dgrStaff, а также по команде Данные контекстного меню p u S t a f f . 6. Создайте обработчик события dgrStaff .OnDblClick, которое происходит при двойном щелчке мышью на сетке данных, и вставьте в него следующий оператор: puData.Popup(Mouse.CursorPos.X,Mouse.CursorPos.Y); В данном случае координаты отображения контекстного меню рпределяются по координатам положения указателя мыши. 7. Назначьте при помощи инспектора объектов в качестве обработчика события prniEmpData.OnClick ТОЛЬКО ЧТО созданный ИСТОД dgrStaffDblClick. 8. Создайте обработчики событий btNewEmp.OnClick и btDeleteEmp.Onclick и введите в них пустой комментарий (реализация этих обработчиков рассмотрена чуть ниже в разделах "Добавление новых сотрудников и редактирование общей информации о сотруднике" и "Удаление данных о сотруднике"). 9. Затем назначьте только что созданные методы btNewEmpClick и btDeleteEmpClick в качестве обработчиков событий pmiNewEmp.OnClick И pmiDeleteEmp.OnClick соответственно. Таким образом связываются команды контекстного меню puStaff Новый и Удалить с соответствующими им кнопками. 10. Создайте обработчик события dgrStaff. OnKeyDown, которое возникает при нажатии клавиши клавиатуры в тот момент, когда курсор находится в сетке данных: procedure TfmMain.dgrStaffKeyDown(Sender:
Shift:
TObject;
var Key:
TShiftState);
begin case key of vk_Return:
if btEmpData.Enabled then
puData.Popup(dgrStaff.ClientOrigin.X, dgrStaff.ClientOrigin.Y); vk_Insert: btNewEmp.Click; vk_Delete: if (Shift = [ssCtrl]) and (btDeleteEmp.Enabled)
Word;
Главная форма
235
then btDeleteEmp.Click
end; end;
В результате при нажатии в компоненте dgrstaf £ клавиши <Enter> (<J>) в его левом верхнем углу отображается контекстное меню puData. Нажатию клавиши соответствует щелчок мышью на кнопке Новый, а нажатию комбинации клавиш — щелчок мышью на кнопке Удалить, П.Сохраните проект и запустите приложение. 12. Поэкспериментируйте с различными вариантами вызова контекстных меню pustaff и puData. Щелкните правой кнопкой мыши на компоненте dgrStaff, а затем выполните команду Данные из контекстного меню. Или же дважды щелкните мышью или нажмите клавишу <Enter> на том же компоненте dgrStaff. 13. Также щелкните мышью на кнопке Данные. 14. После этого закройте приложение и вернитесь в конструктор форм.
Отображение общих данных о сотруднике Общими являются данные о сотруднике, извлекаемые из представления FullList. К дополнительным данным относится информация о членах семьи (таблица FAMILY) и трудовой деятельности (таблица JOBS). Фамилия, имя, отчество, а также название подразделения, к которому относится сотрудник, и его должность отображаются в сетке dgrstaff. Остальные общие данные должны отображаться при помощи компонента sgData. Компонент sgData — это объект класса TStringGrid, расположенный на форме fmMain слева от поля фотографии. Свойство DafaultDrawing компонента sgData имеет значение False. Это свидетельствует о том, что прорисовка содержимого ячеек выполняется программно в обработчике события OnDrawCell (такой подход уже рассматривался при разработке приложения "Лабиринт"). « См. подробнее Б разделе "Рисование структуры лабиринта" главы 2.
1. Но прежде необходимо организовать заполнение ячеек компонента sgData информацией из текущей строки набора данных sidsStaff. Реализуем это при помощи обработчика события dsStaff .OnDataChange, который вызывается при изменении содержимого текущей строки набора данных, связанного с компонентом d s S t a f f : procedure TfmMain.dsStaffDataChange(Sender: TObject;
Field: TField); begin with dmStaff.sidsStaff, sgData do begin
236
Глава 7. Приложение "Персонал" //Отображаем информацию о текущей позиции в наборе данных paCount.Caption := IntToStr(RecNo) + '/' + IntToStr(RecordCount); //Определяем доступность кнопок и команд контекстных меню //на основании количества записей в наборе данных btDeleteEmp.Enabled ;= RecNo > 0; btEmpData.Enabled := btDeleteEmp.Enabled; pmiDeleteEmp.Enabled := btDeleteEmp.Enabled; pmiEmpData.Enabled := btEmpData.Enabled; //Заполняем ячейки компонента sgData Cells [0,0] •Должностные данные' Cells [0,1] 'Подразделение'; Cells [0,2] 'Должность'; 1 Cella[0,3] Табельный номер'; Cells [0,4] 'Зарплата'; Cells [0,5] 'Личные данные'; Cells [0,6] 'Дата рождения'; Cells [0,7] 'Место рождения'; Cells [0,8] 'Идентификационный код'; Cells [0,9] 'Паспорт'; Cells[0,10] 'Адрес'; 1 Cells[0,ll] Индекс'; 'Область'; Cells[0,12] Cells[0,13] 'Район'; Cells[0,14] 'Город/село'; Cells[0,15] 'Улица, номер дома' 'Телефон•; Cells[0,16] = FieldByName('DeptShortNaffle').AsString; Cells [1,2] = FieldByName('PosShortName').AsString; Cells [1,3] = FieldByName('TabNmn')-AsString; C e l l s [ l , 4 ] = FloatToStrF(FieldByName('Salary').AsFloat, ffCurrency,15,2); if FieldByName{'BirthDate').IsNull then Cells[1,6] := '' else Cells[1,6] ;= DateToStr (FieldByName('BirthDate1) .AsDateTime); Cells[l,7] := FieldByNameCBornPlace').AsString; Cells[l,8] := FloatToStrF(FieldByName('TaxCode').AsFloat,ffFixed,12,0); if FieldByName('PasspNum').isNull then Cells[l,9] := ' ' else Cells[l,9] := FieldByName('PasspNum') .AsString + ' от ' + DateToStr(FieldByName('PasspDate').AsDateTime); Cells[l,ll] := IntToStr(FieldByName('Zip')-Aslnteger); Cells[l,12] := FieldByName('Area').AsString; Cells[l,13] := FieldByName('Region').AsString;
Главная форма
237
sgData.Cells[1,14] := FieldByName('City').AsString; Cells[l,15] := FieldByName('Street').AsString + ', '+ FieldByName('House').AsString; Cells[l,16] := FieldByName('Tel').AsString; end; end; В этом методе использовалось несколько функций преобразований. • •
IntToStr — преобразование целого числа в строку типа string. FloatToStrF — преобразование числа с плавающей запятой в форматированную строку. Второй параметр — это тип формата результирующей строки. Типу ffCurrency соответствует формат денежных чисел, а типу f f F i x e d — числа с фиксированной точкой. Третий и четвертый параметры — это соответственно общая разрядность и количество разрядов после запятой. • DateToStr — преобразование даты в строку типа string. 2. Создайте обработчик события sgData.OnDrawCell: procedure TfmMain.sgDataDrawCell(Sender: TQbject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState); begin with sgData, Canvas do if ARow in [0,5,10] then begin //Если отображаются заголовки разделов Brush.Color := clTeal; //Цвет заливки - темно-зеленый Font.Style := [fsBold]; //Шрифт полужирный Font.Color := alWhite; //Цвет шрифта - белый FillReat(Rect); //Заполняем ячейку цветом Brush.Color //Выводим текст TextOut{Rect.Left + 2, Rect.Top + 2, Cells[ACol,Arow]); end else if ACol = 0 then begin //Если отображаются названия полей Brush.Color := clScrollBar; //Заливка - светло-серая Font-Style := []; //Шрифт обычный Font.Color := clBlack; //Цвет шрифта - черный FillRect(Rect); TextOut(Rect.Left + 2, Rent.Top + 2, Cells[ACol,Arow]); end else begin //Если отображаются значения полей
238
Глава 7. Приложение "Персонал" Brush.Color := clWhite; //Заливка - белая FillRect(Rsct); TextOut(Rect.Left + 2, Rect.Top + 2, Calls[ACol,Arow]); end;
end;
Параметру ACol соответствует номер столбца для отображаемой ячейки, а параметру AROW -- номер строки. Все ячейки компонента sgData прорисовываются последовательно, и для каждой из них вызывается метод sgDataDrawCell. Ячейка, которая прорисовывается в данный момент, определяется при помощи параметров ACol и AROW — номера столбца и номера строки. 3. Создайте обработчик события sgData.OnClick. Он будет содержать только один оператор: sgData.Refresh;
Этот оператор означает, что при щелчке мышью на компоненте sgData данные будут прорисовываться по-новому. В противном случае содержимое текущей ячейки будет затираться невидимым курсором выбора. 4. Сохраните проект и запустите приложение. 5. Выберите какое-нибудь подразделение. Теперь на панели paCount должна отображаться текущая позиция в наборе данных (в данном случае — О/О), а в сетке sgData будут отображаться поля общих данных о сотруднике, как показано на рис. 7.11.
Добавление новых сотрудников и редактирование общей информации о сотруднике Для ввода общей информации о новом или уже существующем сотруднике будем использовать отдельную форму. 1. Добавьте в проект новую форму, присвойте ей имя fmNew, а соответствующий программный модуль назовите StfNew.pas. 2. В разделе uses программного модуля stfMain вставьте ссылку на модуль StfNew, а в разделе uses программного модуля StfNew — ссылку на модуль данных stfDMod. 3. Выполните команду Project >• Options и на вкладке Forms, раскрывшегося диалогового окна, перенесите форму fmNew в список Available Forms, так как она будет создаваться динамически, а не при инициализации приложения. « См. подробнее в разделе "Пример динамического создания форм" главы 1.
Форма fmNew 1, Присвойте свойству fmNew.Height значение 390, свойству fmNew.Width значение 748, а свойству Position — значение poScreenCenter.
Главная форма
239
Дата рожижия М вето ро*ленин Идвн™т»дцис»*ыЗ код Паспорт
Область Район Город/село 9*ш. номер дома .-
Рис. 7.11. Теперь отображается текущая позиция в наборе данных'sidsstaff, a также поля общей информации о сотруднике 2. Расположите на форме fmHew компоненты в соответствии с листингом 7.4. Листинг 7.4. Компоненты, расположенные на форме frnNew object paData: TPanel Height = 310 align = alTop object imPhoto: TImage Width = 250 Align = alRight Center = True end object. valeData: TValueListEditor Align = alClient DisplayOptions = [doAutoColResize, doKeyColFixed] Strings. Strings = (
240
Глава 7. Приложение "Персонал*
Окончание листинга 7.4 'Фамилия=' 'Имя=' 1 Отчество=' 'Подразделение=' 'Должность=' 'Зарплата=' 'Табельный номер=' 'Дата рождения=' 'Место рождения=' 1 Идентификационный код=' 'Почтовый индекс='' 'Улица=' 'Номер дома=' 'Телефон^' 'Номер ласпорта=' 'Дата выдачи паспорта=') ColWidths = ( 142 339) end end object bbOK: TBitBtn Left = 169 Top = 332 Width = 100 Height = '25 Kind = bkOK end object bbCancal: TBitBtn Left = 321 Top = 332 Width = 100 Height = 25 Caption = 'Отменить' Kind = bkCancel . end object bbPhoto: TBitBtn Left = 469 Top =332 Width - 100 Height = 25 Caption = 'Фотография' end
Главная форма
241
3. Можете назначить при помощи инспектора объектов свойству Glyph кнопки Фотография (bbPhoto) какую-нибудь походящую пиктограмму. После расположения компонентов форма fmHew должна выглядеть примерно так, как показано на рис. 7.12.
1
7- fmNew
Имя
Отчество Подразделение Должность Зарплате Дата рождения Место рождения Почтовый индекс UMU
Номер дома Номер паспорта Дата выда*! паспорта
\
Отмвшгь
Рис. 7.12. Форма fmNew после размещения компонентов
Как видите, эта форма весьма проста. На ней расположены только уже рассмотренные в ЭТОЙ книге компоненты Panel, ValueListEciitor, Image и BitBtn. В компоненте valeData значения для полей Подразделение, Должность и Почтовый индекс должны выбираться из четко определенных наборов значений. Эти наборы значений будут формироваться на основании данных из таблиц DEPS, POSS и REGIONS, а затем назначаться соответствующим свойствам valeData. ItemProps [и] .PickList. Для выполнения этих действий создадим отдельную функцию.
Работа с компонентом valeData 1. Объявите в разделе private класса TfmMew (модуль stfNew.pas) процедуру FillPickList: private { Private declarations } procedure FillPickList(CurRow: integer; sids: TSimpleDataSet; FieldName: string);
В эту процедуру передается три параметра: номер строки в наборе значений valeData, для которой формируется список PickList;
ссылка на набор данных;
242
Глава 7. Приложение "Персонал"
• названия полей, из которых формируется список PickList. Создайте в разделе implementation реализацию процедуры FillPickList: procedure TfmHew. FillPickList (CurRow: integer; sids : TSimpleDataSet; FieldName: string) ; begin with valeData, sids do begin //Поле — раскрывающийся список ItemProps [CurRow] .EditStyle : = esPickList; ItemProps [CurRow] .Readonly := True; //Ручной ввод запрещен //Начинаем формировать список ItemProps [CurRow] . PickList . Clear ; while not EOF do begin ItemProps [CurRow] . PickList. AddfFieldByName (FieldName} .AsString) ; Next ; end; end; end ,_
2. Вставьте в разделе uses ссылку на модуль SimpleDS, в котором реализован класс TSimpleDataSet. 3. Создайте обработчик события fmHew.OnActivate: procedure Т fnLNew. FormActivate (Sender : TOb j ect) ; begin with valeData do begin //Поле Подразделение FillPickList(3,dmStaff.sidsDeps,'DeptFullName1); //Поле Должность FillPickList(4,dmStaff.sidsPoss,'PosFullName'); //Поле Почтовый индекс FillPickList(10,dmStaff.sidsRegions,'Zip'); //Поле Дата рождения ItemProp9[7].EditMask := '!99/99/0099;l;_'; //Поле Идентификационный код ItemProps[9].EditMask := '!000000000000;1;_'; //Поле Дата выдачи паспорта ItemProps[15].EditMask := '!99/99/0099;!;_'; Col := I; //Устанавливаем курсор во второй столбец end; end;
Главная форма
243
Значения полей Подразделение, Должность И Почтовый индекс выбираются исключительно из раскрывающихся списков, а значения полей Дата рождения и Дата выдачи паспорта вводятся в соответствии с маской даты. Маска поля Идентификационный код подразумевает ввод исключительно цифр. В формах, подобных fmNew, желательно выполнять какую-нибудь проверку корректности введенных данных. Например, в данном примере кнопка ОК может быть доступна только в том случае, если введены какие-либо данные в полях Фамилия, Имя, Отчество, Подразделение, Должность, Зарплата, Табельный номер, Идентификационный код, Почтовый индекс И Дата рождения. Реализуем соответствующую проверку в отдельной процедуре. 4. Объявите закрытую процедуру CheckButtonOK: private { Private declarations } procedure CheckButtonOK; 5. Создайте реализацию этой процедуры в разделе implementation: procedure TfmHew.CheckButtonOK; begin with valeData do bbOK.Enabled := (Values['Фамилия1] <> '') and (Values['Имя'] о '') and (Values['Отчество1] <> '') and (Values['Подразделение'] <> '') and (Values['Должность'] <> '') and (Values['Зарплата'] о ''). and (Values['Табельный номер']•о '') and (Values['Идентификационный код'] <> '') and (Values['Почтовый индекс1] <> '') and (Values['Дата рождения'] <> ' . . '); end;
6. Создайте обработчик события valeData.OnValidate, который вызывается после редактирования поля и обычно используется для проверки корректности введенных значений: procedure•TfmHew.valeDataValidate(Sender: TObj ect; ACol, ARow: Integer; const KeyHame, KeyValue: String); begin with valeData do begin //Делаем первые буквы фамилии, имени и отчества прописными if ARow < 3 then
244
Глава 7. Приложение "Персонал"
Values[Keys[ARow]] := AnsiUpperCase(copy(KeyValue,1,1))+ copy(KeyValue,2,length(KeyValue)-1); CheckButtonOK; //Проверяем доступность кнопки OK end; end; В этой процедуре выполняется обработка значений, введенных в первых трех полях: Фамилия, Имя и Отчество. Первые буквы фамилии, имени и отчества становятся прописными. Свойство valeData.Keys — это набор заголовков, а свойство valeData. Values — набор значений полей. Поиск в наборе строк Keys выполняется по номеру, а поиск в наборе строк Values — по заголовку поля. Для преобразования первой буквы фамилии, имени и отчества используется функция AnsiUpperCase. Функция сору-выделяет подстроку из строки, указанной в качестве первого параметра. Начальная позиция подстроки указывается во втором параметре, а длина — в третьем параметре функции сору. После редактирования каждого поля в компоненте valeData выполняется проверка доступности кнопки ОК. 7. Кроме того, вызов процедуры CheckButtonOK необходимо добавить в обработчик события fmHew.OnActivate: procedure begin
TfraNew.Formfictivate(Sender:
TObject);
CheckButtonOK; //Проверяем доступность кнопки OK
end;
Загрузка фотографии Последнее, что необходимо сделать в модуле stfNew, — это реализовать выбор файла с фотографией сотрудника. 1. Для этого расположите в модуле данных компонент OpenPictureDialog с вкладки Dialogs палитры компонентов и присвойте его свойству Name значение opd_l. 2. Дважды щелкните мышью в поле свойства opd_l. Filter инспектора объектов и удалите все фильтры, кроме Bitmaps (* .bdp}. 3. Перейдите в конструкторе к форме fmNew, создайте обработчик события bbPhoto. onclick и вставьте в него следующий программный код: with dmStaff.opd_l do begin FileName := ''; if Execute then imPhoto . Picture. LoadFrornFile (FileName) ; end;
Главная форма
245
Здесь при помощи компонента opd_l реализован вызов стандартного диалогового окна открытия графического файла. При этом отображаются только файлы с расширением *.bmp. Содержимое выбранного файла загружается в компонент iraPhoto. СОВЕТ Многоцветные графические файлы в формате * -Ьтр могут иметь достаточно большой размер, поэтому для более рационального использования ресурсов базы данных лучше всего использовать небольшие черно-белые, 4-битные изображения (16 цветов) или 8битные изображения (256 цветов: серые или цветные). Для качественного отображения фотографий на экране компьютера достаточно использовать изображения низкого разрешения— 72 dpi (точек на дюйм).
Процедура DataForEmployee Реализуем в модуле формы fmMain создание формы fmNew и обработку значений, введенных в компоненте fmNew.valeData, как при добавлении нового сотрудника, так и при редактировании уже существующих данных. Для этого создадим отдельную процедуру. 1. Объявите в разделе private класса TfmMain процедуру DataForEmployee: private
{ Private declarations } procedure DataForEmployee(Creation: boolean); При добавлении нового сотрудника параметр Creation имеет значение True, а при редактировании уже существующих данных — значение False. 2. Создайте реализацию этой процедуры в разделе implementation: procedure TfmMain.DataForEmployee(Creation: boolean); begin Application.CreateForm(TfmHew, fmNew); with fmNew, valeData, dmStaff do if Creation then Caption := 'Новый сотрудник1 else begin //Если редактируются существующие данные Caption := 'Редактирование общей информации о сотруднике'; //Находим по идентификаторам подразделение и должность sidsDeps.Locate('DeptID1,sidsStaffDepID.As Integer, []); sidsPoss.Locate('PosID1,sidsStaffPosID.flsInteger,[]); //Заполняем список значений полей Values['Фамилия'] := sidsStaffLastName.AsString; Values['Имя*] := sidsStaffFirstName.AsString; Values [ 'Отчество' ] •'. = sidsStaf fFatherName .As String;
246
Глава 7. Приложение "Персонал" 1
Values['Почтовый индекс ] := IntToStr(sidsStaffZip.AsInteger); 1 Values['Улица ] := sidsStaffStreet.AsString; Values['Номер дома/] := sidsStaffHouse-AsString; 1 Values['Телефон ] := sidsStaffTel.AsString; Values['Идентификационный код'] := FloatToStrF(sidsStaffTaxCode.AsFloat,ffFixed, 12, 0); 1 Values['Табельный номер ] := sidsStaffTabNum.AsString; Values['Дата рождения'] := DateToStr(sidsStaffBirthDate.AsDateTime); 1 Values['Подразделение ] := sidsDepsDeptFullNaine.AsString; Values['Должность'] := sidsPossPosFullName.AsString; Values['Место рождения'] := sidsStaffBornPlace.AsString; Values['Зарплата'] := FloatToStrFfSidsStaffSalary.AsFloat,ffFixed,15,2) ; if not sidsStaffPasspNum.IsNull then begin Values['Номер паспорта'] := sidsStaffPasspNum.AsString; f 1 Values[ flaTa выдачи паспорта ] := DateToStr(sidsStaffPasspDate.AsDate!ime); end; imPhoto.Picture.Assign(sidsStaffPhoto); end; if fmNew.ShowModal = mrOK then / /Если в форме frnNew была нажата кнопка OK with dmStaff, sidsStaffHid do begin //Находим записи, соответствующие //новым подразделению и должности sidsDeps.Locate('DeptFullName', fmNew.valeData.Values[подразделение1], []); sidsPoss-Locate('PosFullNarae', ' fmNew.valeData.Values['Должность'] , []J ; if Creation //Если добавляем нового сотрудника,... then Append //тогда добавляем в таблицу STAFF новую запись else Locate('ID1,sidsStaffID.Aslnteger,[]}; //иначе находим //запись для редактирования Edit; //Открываем запись на редактирование FieldByNaitie (' ID 1 ) .Aslnteger := sidsStaf f ID. As Integer; FieldByNarae('LastName1).AsString := fraNew.valeData.Values['Фамилия']; FieldByName('FirstName').AsString := fmNew.valeData.Values['Имя']; FieldByName('FatherName').AsString :=
Главная форма
247
fmNew.valeData.Values['Отчество']; FieldByName('Zip').AsInteger := StrToInt(fmNew.valeData.Values['Почтовый индекс1 FieldByName('Street').AsString := fmNew.valeData."Values['Улица']; FieldByName{'House1).AsString := ^ дома']; fmNew.valeData.Values['Номер FieldByName{'Tel').AsString :fmNew.valeData.Values['Телефон']; FieldByName('TaxCode').AsFloat := StrToFloat(fmNew.valeData.Values['Идентификационный код']]; FieldByName('TabNum').ftsString := fmNew.valeData.Values['Табельный номер']; FieldByName('BirthDate'].AsDateTime := StrToDate(fmNew.valeData.Values['Дата рождения']); FieldByName('DepID').As Integer := sidsDepsDeptID. As Integer; FieldByName('PosID'}.AsInteger := sidsPossPosID.Aslnteger; FieldByName('BornPlace').AsString := fmNew.valeData.Values['Место рождения']; FieldByName('Salary').AsFloat := StrToFloat(fmNew.valeData.Values['Зарплата1]); if fmNew.valeData.Values['Номер паспорта'] О '' then ' begin FieldByName('PasspNum').AsString := fmNew.valeData.Values['Номер паспорта']; FieldByName{'PasspDate'].AsDateTime := StrToDate(fmNew.valeData.Values['Дата выдачи паспорта']); end; FieldByName('Photo1).Assign(fmNew.imPhoto.Picture); Post; //Сохраняем внесенные изменения //в наборе данных sidsStaffMid ApplyUpdates(0); //Передаем изменения на сервер Refresh; //Обновляем набор данных sidsStaffMid SetSelection; //Выолняем повторную выборку данных //о сотрдуниках //Находим сотрудника/ с данными которого только что работали //в наборе данных sidsStaffMid Locate{'Lastname;FirstName;FatherName;TabNum', VarArrayOf([fmNew.valeData.Values fФамилия'], fmNew.valeData.Values['Имя'], fmNew.valeData.Values['Отчество'], fmNew. valeData. Values [ ' Табельный но'мер ' ] } ) , [ ] ) ; //...и в наборе данных sidsStaff
248
Глава 7. Приложение "Персонал" sidsStaff.Locate! 'ID',FieldByName('ID').Asinteger, []); end;
fmNew.Free; end; Для добавления "пустой" записи в набор данных Simple Data Set используется метод Append, а для перехода в режим редактирования этой записи — метод Edit. Для сохранения изменений в клиентском наборе данных используется метод Post. Обратите внимание на следующие особенности. • Для занесения фотографии в поле Photo используется не обычный оператор присваивания, а метод Assign. • В вызове метода Locate функция VarArrayOf используется для формирования одномерного вариантного массива на основании списка некоторых значений. НЕМНОГО ТЕОРИИ Иногда бывает необходимо работать с данными, тип которых на этапе компиляции не определен. В таких случаях можно использовать специальный тип Variant. Переменные и параметры этого типа во время выполнения приложения могут принимать разнотипные значения. Вариантный массив — это массив значений типа Variant. Для создания таких массивов используются функции VarArrayCreate И VarArrayOf. Осталось добавить в программный код модуля StfMain вызовы процедуры DataForEmployee. Для этого выполните следующие действия. 3, Найдите в исходном коде обработчик события btNewEmp.OnClick (кнопка Новый). 4, Создайте обработчик события pmiCommonData. OnClick (команда Общие контекстного меню puData): procedure
TfmMain.btNewEmpClick(Sender:
TObject);
begin DataFoirEmployee(True) ; end;
procedure TfmMaiti.pmiCoramonDataClick(Sender: begin DataForEmployee(False); end;
TObject);
5. Сохраните проект и запустите приложение. 6. Выберите в иерархии подразделений какой-нибудь элемент, а затем щелкните мышью на кнопке Новый. 7. Введите в форме Новый сотрудник необходимые данные, выберите файл с фотографией и щелкните мышью на кнопке ОК (рис. 7. 13).
Главная форма
249
.- OI
/::'НГ»БЫЙ г труд ник Шпак Имя
Юрий
Отчестве,
Алекснвич
Должность
Начальник
Зарплата
1000
Табелыый номер
QCQ1
Дата ро*дения
07СШ974
Место рождения
cJopa
Рдовадство
П1111111И1
Почтам шпеке
3324
Номер дома Телефон Номер паспорта Лага выдачи паспорта
!1тменшт* j
j
а
Фата-рафия
Рис. 7.13. Добавление нового сотрудника В результате информация о сотруднике будет добавлена в базу данных (рис. 7,14). Обратите внимание на то, что теперь кнопки Данные и Удалить стали доступными. 7 ' Персона п
JTLB СотИВННЯИ
Параметры приломенчя
Слцаиоччки
- Пог»Я-спин* Р: Работавшие
1Л
Руноищство Отдел кадров Уволенные
Г" Соргчзовать по
|по Фамилии |0т
|ЛпЯнгн^р]1ч
Подрадделенив Доит [РикпрппгтР
Данные
1 OOODO грн Дата рокдепщ
07 OS 1974
•Лесто рожцениР
с.Гора 111111111111
Паспорт ВЭ24 Область
Кчевоая
Район ород/село
Гора
(чща. HtntDjW-^ Телефон
Рис. 7,14. Новый сотрудник
Ышшгв
250
Глава 7. Приложение "Персонал"
8. Щелкните мышью на кнопке Данные и выполните команду Общие контекстном меню. 9. Внесите какие-нибудь изменения в информацию о сотруднике и щелкните мышью на кнопке ОК. 10. Внесенные коррективы будут сохранены в базе данных и отобразятся на форме fmMain. 11. Закройте приложение и вернитесь в режим конструктора форм.
Удаление данных о сотруднике Реализуем удаление данных о сотруднике из базы данных. Для этого можно было бы просто удалить запись в наборе данных sidsStaffMid при помощи метода TSimpleDataSet. Delete, а затем вызвать метод ApplyUpdates. Однако поступим иначе, и для удаления записи из таблицы STAFF воспользуемся вызовом хранимой процедуры DeleteEmp. I. Перейдите к обработчику события btDelete.OnClick, дважды щелкнув мышью в форме fmMain на кнопке Удалить, и добавьте в него следующий программный код: procedure T f m M a i n . b t D e l e t e E m p C l i c k ( S e n d e r : var TD: TTransactionDesc;
TObject);
begin
if MessageDlg('Удалить запись?', mtConfirmation,[mbYes.mbNo],0) = mrYes than with dmStaff do begin try
TD.TransaationlD := 1; //Идентификатор транзакции TD.IsolationLevel := xilREADCOMMITTED; //Уровень разграни//чения транзакции sqlcStaff.StartTransaction(TD); //Инициализируем транзакцию with sqldsDeleteEmp do begin //Передаем параметр в хранимую процедуру ParamByName('pEmpID').Aslnteger := sidsStaffID.AsInteger; ExecSQL; //Выполняем хранимую процедуру end;
sqlcStaff.Commit(TD); //Завершаем транзакцию //командой COMMIT sidsStaff.Delete; //Удаляем запись в клиентском //наборе данных except //В случае неудачного выполнения хранимой процедуры on E: Exception do
Главная форма
251
begin //Выполняем отмену изменений командой ROLLBACK sqlcStaff.Rollback(ID); 1 MessageDlg('Ошибка удаления записи! + Й13+Е.Message,mtError,[mbok],0);
end; end; end; end;
Объект то класса TTransactionDesc используется идя инициализации транзакции. При этом требуется присвоить определенные значения свойствам TD.TransactionID (идентификатор транзакции) и то. isolationLevel (уровень разграничения транзакции). НЕМНОГО ТЕОРИИ Уровень разграничения транзакций (transaction isolation level —TIL) определяет возможность доступа конкурирующих транзакций к внесенным изменениям и способность транзакции "видеть" изменения, внесенные другими транзакциями. • • •
•
Свойство IsolationLevel может принимать одно из следующих значений. xilDIRTYREAD - - видимы не сохраненные изменения, сделанные другими транзакциями. xilREADCOMMITTED — видимы только сохраненные изменения. xilREPEATABLEREAD -- изменения, внесенные другими транзакциями в ранее прочитанные данные, невидимы. Другими словами, на этом уровне разграничения каждый раз, когда транзакция считывает данную запись, она всегда получает одну и ту же запись. xilcuSTOM — неопределенный уровень разграничения.
Транзакция инициализируется вызовом метода TSQLConnection. startTransaction. После этого все изменения, переданные базе данных в текущей транзакции, могут либо быть подтверждены вызовом метода TSQLConnection.Commit, либо отменены вызовом метода TSQLConnection. Rollback. 2. Для того чтобы использовать класс TTransactionDesc, необходимо указать в разделе uses модуля stfMain ссылку на модуль DBXpress, в котором этот класс реализован. Если хранимая процедура DeleteEmp выполняется корректно, то после вызова метода Commit из таблицы STAFF удаляется соответствующая .запись. Затем в процедуре btDeleteEmpdick удаляется запись из клиентского набора данных sidsStaff. Если хранимая процедура не может корректно выполнить операцию удаления строки, то возникает исключительная ситуация, в результате чего начнет
252
Глава 7. Приложение "Персонал"
выполняться блок операторов exception. В этом блоке вначале выполняется отмена операции удаления при помощи метода Rollback, а затем на экран выводится сообщение об ошибке. 3. Сохраните проект и запустите приложение. 4. Удалите созданную ранее запись о сотруднике, щелкнув мышью на, кнопке Удалить, или выполнив соответствующую ей команду контекстного меню.
Автоматическая вставка данных в таблицу JOBS Таблица JOBS — это аналог трудовой книжки, в которой хранится информация обо всех трудовых отношениях сотрудника. Было бы неплохо реализовать автоматическую вставку данных в таблицу JOBS при добавлении нового сотрудника, а также при его переводе в другое подразделение или на другую должность. Для этого необходимо запомнить название подразделения и должности при открытии формы fmMew, а затем сравнить эти значения с названием подразделения и должности после закрытия формы fmNew. Если в одном из названий есть различия, то это указывает на необходимость изменения данных таблицы JOBS.
Форма fmTransfer При обнаружении перевода в другое подразделение или на другую должность необходимо каким-то образом указать дату и причину этого перевода, а также номер соответствующего приказа. Для ввода этой информации создадим отдельную форму. 1. Добавьте в проект новую форму, присвойте ей имя fmTransfer, а соответствующий программный модуль назовите StfTrans.pas. 2. В разделе uses программного модуля StfMain вставьте ссылку на модуль StfTrans. 3. Выполните команду Project >- Options и на вкладке Forms раскрывшегося диалогового окна, перенесите форму fmTransfer в список Available Forms, так как она будет создаваться динамически, а не при инициализации приложения. 4. Присвойте свойству fmTransfer.Caption значение Перевод сотрудника, свойству fmTransfer .Height — значение 385, свойству f m T r a n s f e r . Position — значение poScreenCenter, а свойству fmTransfer .Width — значение 540.
5. Расположите на форме fmTransfer компоненты в соответствии с листингом 7.5. Листинг 7.5. Компоненты, расположенные на форме fmTransfer object laDate: TLabel L e f t = 16 Top = 256 Caption = 'Дата перевода,: '
Главная форма Продолжение листинга 7.5 end object laCauses: TLabel Left = 52 Top = 2 8 8 Caption = 'Причина:1 end object laOrder: TLabel Left = 2 2 4 Top = 256 Caption = 'Приказ №' end object laedName: TLabeledEdit Left = 12 Top = 24 Width = 508 TabStop = False Anchors = [akLeft, akTop, akRight] Color = clScrollBar EditLabel.Caption = 'Сотрудник' Readonly = True end object laedDepFirom: TLabeledEdit Left = 12 Top - 72 Width = 485 TabStop = False Anchors = [akLeft, akTop, akRight] Color = clScrollBar EditLabel.Caption = 'был переведен из подразделения' Readonly = True end object laedDepTo: TLabeledEdit Left = 40 Top = 1 1 6 Width = 4 3 1 TabStop = False Anchors = [akLeft, akTop, akRight] Color = clScrollBar EditLabel.Caption = 'в подразделение1 Readonly = True end
253
254
Глава 7. Приложение "Персонал"
Продолжение листинга 7.5 object laedPosFrom: TLabeledEdit Left = 12 Top = 164 Width = 485 TabStop = False Anchors = [akLeft, akTop, akRight] Color = clSorollBar EditLabel.Caption = 'С должности' Readonly = True end object laedPosTo: TLabeledEdit Left = 40 Top = 208 Width = 481 TabStop = False Anchors = [akLeft, akTop, akRight] Color = clScrollBar EditLabel.Caption = 'на должность' Readonly = True end object dtpDate: TDateTimePicker Left = 100 Top = 2 4 8 Width = 85 end object cbCauses: TComboBox Left = 100 Top = 280 Width = 421 Anchors = [akLeft, akTop, akRight] Sorted = True Items.Strings = ( 'В связи с ликвидацией организации' 1 В связи с ликвидацией подразделения' 'В связи с сокращением штата' 'По собственному желанию') end object ЬЬОК: TBitBtn Left = 208 Top = 324 Width = 129
Главная форма
255
Окончание листинга 7.5 Kind = bkOK end object edOrderNum: L e f t = 280
TEdit
Top = 248 Width = 121 end
Внешний вид формы fmTransfer должен соответствовать рис. 7.15. 7 Перевод сотрудника Сстрданик
был перевода из
в подразделение
:
С должности'
• на должность-
:: Првйинж!
Рис. 7.15, Форма fmTransfer
Изменения процедуры Data For Employee Вставьте в процедуру DataForEmployee, реализованную в модуле StfMain, строки, выделенные в представленном ниже фрагменте полужирным шрифтом: procedure TfmMain.DataForEmployee(Creation: boolean) ; var CurDep, CurPos: string[100]; //CurDep - первоначальное название подразделения //Cur&os - первоначальное название должности begin Appl? cation. CreateForm(TfmNew, fmNew) ,with fmNew, valepata, dmStaff do if Creation then begin Caption := 'Новый сотрудник 1 ; CurDep := ''; CurPos := '';
256
Глава 7. Приложение "Персонал'1 end else begin sidsРозs.Locate('PosID1,sidsStaffPosID.AsInteger,[]); //Запоминаем названия подразделения и должности сотрудника CurDep := sidsDepsDeptFullName.AsString; CurPos := sidsPossPosFullName.AsString;
end; if fmNew.ShowModal = mrOK then with dmStaff, sidsStaffMid do begin
sidsStaff-Locate{'ID',FieldByName('ID').Asinteger,[]); //Если были изменены подразделение или должность if (CurDep О fmNew.valeData.Values['Подразделение']) or (CurPos <> fmNew.valeData.Values['Должность']) then SetTransfer(FieldByName(TID').Asinteger, FieldByName('LastName').AsString, FieldByName('FirstName') .AsString, FieldByName('FatherName').AsString, CurDep, fmNew.valeData.Values['Подразделение'], CurPos,fmNew.valeData.Values['Должность']); end; fmNew.Free; end; ^ __ ^_ _ W Процедура SetTransfer, вызываемая в конце процедуры DataForEmployee, рассмотрена в следующем разделе этой главы.
Переменные CurDep и CurPos используются для сохранения названия подразделения и должности сотрудника в момент открытия формы fmNew. В конце процедуры DataForEmployee значения этих переменных сравниваются со значениями, введенными в соответствующие поля компонента fmNew.valeData. Если хотя бы одно из значений не совпадает, тогда вызывается процедура SetTransfer. В этой процедуре, собственно, и реализовано создание формы fmTransfer и вставка данных в таблицу JOBS!
Процедура SetTransfer 1. Объявите в разделе private класса TfmMain процедуру SetTransfer: private
EmpFatherNane, OldDep, NewDep, DldPos, NewPos: string); В эту процедуру в качестве параметров передается идентификатор сотрудника, его фамилия, имя и отчество, а также названия старого и нового подразделения и (или) должности. 2. Создайте в разделе implementation реализацию этой процедуры: procedure TfmMain.SetTransfer(EmpID: integer; EmpLastName, EmpFirstName, EmpFatherName, OldDep, NewDep, OldPos, NewPos: string); begin Application. Great eForia(TfmTransfer,fmTransfer); with fmTransfer, dmStaff, sidsJobs do begin //Выводим информацию о переводе laedName.Text := EmpLastName + ' ' + EmpFirstName + ' ' + EmpFatherName; laedDepFrom.Text := OldDep; laedDepTo.Text : = NewDep; laedPosFrom.Text := OldPos; laedPosTo.Text := NewPos; dtpDate.Date := Now; ShowModal; //Отображаем форму fmTransfer if Locate ( ' EmpID; StopDate ' , VarArrayOf ( [ErnpID, null ] ) , [ ] } then begin //Если в таблице JOBS для текущего сотрудника //есть запись, в которой поле StopDate не заполнено Edit; //Вносим в эту запись: sidsJobsStopDate.AsDateTime := dtpDate.Date; //дату перевода sidsJobsStopCauses.AsString := cbCauses.Text; //причину //перевода нidsJobsQutOrderNum.AsString := edOrderNum.Text; //номер //приказа Post,end; //Добавляем запись для нового подразделения или //новой должности InsertRecord([EmpID,dtpDate.Date, nil, ReadStrParamj'Организация1], NewDep, NewPos, ' ' , '!' , edOrderNuia. Text, ' '] ) ; ApplyUpdates(О); //Передаем изменения на сервер end;
258
Глава 7. Приложение "Персонал"
fmTransfer.Free; end;
Обратите внимание на функцию Now. Эта функция возвращает текущую дату. Существует еще одна, подобная ей функция, — Time, которая возвращает текущее время. Если а наборе данных sidsjobs для текущего сотрудника найдена запись, в которой поле stopDate (дата окончания трудовой деятельности на той или иной должности) не содержит никакого значения, то такая запись соответствует текущей должности этого сотрудника. В таком случае необходимо ввести в эту* запись дату (поле StopDate) и причину (поле stopCauses) окончания трудовой деятельности на последней должности, а также номер соответствующего приказа (поле Out Order Hum). После этого в набор данных sidsJobs добавляется запись о новой должности. При этом информация вводится в поля EmpID (идентификатор сотрудника), StartDate (дата перевода на должность), Organization (название организации), Dep (название подразделения), Роз (название должности), CurOrg (признак текущей организации), inOrderNum (приказ о зачислении на должность). Дата перевода со старой должности совпадает с датой зачисления на новую. 3. Сохраните проект и запустите приложение. 4. Добавьте в любое подразделение нового сотрудника. 5. При закрытии формы ввода общих данных о сотруднике на экране отобразится форма Перевод сотрудника. Укажите в этой форме дату зачисления на новую должность и номер приказа, и щелкните мышью на кнопке ОК. 6. Откройте форму редактирования общих данных о сотруднике и измените подразделение или должность. 7. После щелчка мышью на кнопке ОК, на экране опять появится форма Перевод сотрудника, где можно указать дату и, в случае необходимости, причину перевода, а также номер приказа. Таким способом можно переводить сотрудников в категорию уволенных, так как этой категории соответствует условное подразделение Уволенные. Просмотр изменений в таблицах FAMILY и JOBS будет реализован в следующем разделе. Однако, предварительно можно просмотреть содержимое этих таблиц, например, при помощи программы IBConsole
Работа с таблицами FAMILY и JOBS Создадим еще одну форму, в которой будет организован доступ к таблицам FAMILY И JOBS.
I. Добавьте в проект новую форму, присвойте ей имя fmData, а соответствующий программный модуль назовите stfData.pas.
Главная форма
259
2. В разделе uses программного модуля stfMain вставьте ссылку на модуль StfData, а в разделе uses программного модуля stfData — ссылку на модуль StfDMod. 3. Выполните команду Project >- Options и на вкладке Forms раскрывшегося диалогового окна, перенесите форму fmData в список Available Forms. 4. Присвойте свойству fmData.Height значение 516, свойству fmData.Caption —• значение Дополнительные данные о сотруднике, свойству fmData. Position значение poScreenCenter, а свойству fmData.Width — значение 730. 5. Расположите на форме fmData компоненты в соответствии с листингом 7.6. Листинг 7.6. Компоненты, расположенные на форме fmData object dsJobs: TDataSouroe DataSet = drnStaf f. sidsJobs end object dsFamily: TDataSource DataSet = dmStaff.sidsFamily end object gbJobs: TGroupBox Height = 225 Align = alTop Caption = 'Трудовая деятельность' object dnaJobs: TDBNavigator Height = 23 • DataSource = dsJobs Align = alBottom end object paJobs: TPanel Width =497 Align = alRight object laOrganization: TLabel Left = 28 Top = 1 6 Caption = 'Организация:' end object laTJept: TLabel Left = 16 Top = 44
Caption = 'Подразделение:' end object laPos: TLabel Left = 36 Top = 72
26О
Глава 7. Приложение "Персонал"
Продолжение листинга 7.6 Caption = 'Должность:' end object laCauses: TLabel Left = 20 Top = 100 Caption"= 'Причина увольнения:' end object lalnOrder: TLabel Left - 16 Top = 128 1 Caption = 'Приказ о назначении: end object laOutOrder: TLabel Left = 12 L Top = 156 Caption = 'Приказ об увольнении:* end object dedOrganization: TDBEdit Left = 100 Top = 8 Width = 385 Anchors = [akLeft, akTop, akRight] DataField = 'ORGANIZATION1 DataSource = dsJobs end object dedDept: TDBEdit J Left = 100 Top = 36 Width = 385 Anchors = [akLeft, akTop, akRight] DataField = ''DEP' DataSource = dsJobs end object dedPos: TDBEdit Left = 100 Top = 64 Width - 385 Anchors = [akLeft, akTop, akRight] DataField = 'PCS' DataSource = dsJobs end
Главная форма Продолжение листинга 7.6 object dcbCauses: TDBComboBox L e f t = 132 Top = 92
Width = 353 Anchors = [akLeft, akTop, akRight] DataField = 'STOPCAUSES* DataSource = clsJobs Items.Strings = ( 'В связи с ликвидацией организации' 'В связи с ликвидацией подразделения' 'В связи с сокращением штата 1 'По собственному желанию1) end object dedlnOrder: TDBEdit Left = 1 3 2 Top = 120 Width = 1 2 1 DataField = 4NORDERNUM1 DataSource = dsJobs end object dedOutOrder: TDBEdit Left = 132 Top =. 148 Width = 1 2 1 DataField = 'OUTORDERNUM' DataSource = dsJobs end end object dgrJobs: TDBGrid Align = alClient DataSource = dsJobs Options = [dgEditing, dgTitles, dglndicator, dgColumnResi ze, dgColLines, dgRowLines, dgTabs, dgAlwaysShowSelection, dgConfirmDelete, dgCancelOnExit] end end object bbOK: TBitBtn Left = 312 Top = 460 Width = 109
261
262
Глава 7. Приложение "Персонал"
Продолжение листинга 7.6 Height = 25 Kind = bkOK and object gbFamily: TGroupBox Height - 225 Align = alTop Caption = 'Семья' object dnaFamily: TDBNavigator Height = 23 DataSource = dsFamily Align = alBottorn end object dgrFamily: TDBGrid Align = alClient DataSource = dsFamily Options = [dgEditing, dgTitles, dglndicator, dgColumnResize, dgColLines, dgRowLines, dgTabs, dgAlwaysShowSelection, dgConfirmDelete, dgCancelOnExit] end end Теперь внешний вил формы fmData должен соответствовать рис. 7.16.
т
?. Дополнительные данные ч согр^Д№«е Тр^цсеая деягеяьнссть
Падраздела*»^ Должность I 1 Причина дольш Приазоиааачечм l При;» об ^воявтм
Сегьп
Рис. 7.16. Форма fmData
Главная форма
263
Объекты столбцов компонента DBGrid 1. В конструкторе форм дважды щелкните мышью на компоненте dgrFamily. В результате на экране появится редактор объектов типа TCoiumn — столбцов сетки данных. Для хранения набора таких объектов предназначено свойство TDBGrid. Columns. Для каждого объекта класса TColumn можно указать собственное значение выравнивания текста (свойство Alignment), цвета (свойство Color) и т.д. 2. Щелкните в редакторе столбцов правой кнопкой мыши и выполните команду Add All Fields (Добавить все поля) раскрывшегося контекстного меню. В результате будут fine is созданы объекты для всех столбцов таблицы FAMILY. 1-B1RTHDATE I-KINNAME 3. Удалите объект, соответствующий полю EmpiD. Теперь сетка данных содержит только три столбца (рис. 7.17). 4. Выделите в редакторе столбцов первый объект, . соответствующий полю Kin, перейдите в инспектор объектов и дважды щелкните мышью в Рис. 7.17. Редактор поле свойства PickList. Если свойство столбцов компонента PickList содержит набор строк, то значение DBGrid поля в сетке данных выбирается из раскрывающегося списка. 5. Вставьте в редакторе свойства PickList следующие строки: Жена Муж Сын
Дочь Мать Отец
•<
Этот набор соответствует возможным значениям поля Kin (Родство).
Создание и активизация формы fmData 1. Перейдите в конструкторе форм к форме fmMain и создайте обработчик события Onclick для команды Дополнительные контекстного меню puData. 2. Вставьте в него следующий программный код: with dmStaff do begin Application.CreateFormfTfmData,fmData); //Просматриваем только данные по текущему сотруднику sidsJobs.SetRange([sidsStaffID.Aslnteger] ,
264
Глава 7. Приложение "Персонал"
[sidsStafflD.AsInteger]); sidsJobs.First; sidsFamily.SetRange([sidsStaffID.изInteger] , [sidsStafflD.AsInteger]); sidsFamily.First; //Заголовок формы - Ф . И . О . f m D a t a . C a p t i o n := s i d s S t a f f L a s t N a m e . A s S t r i n g +
'
sidsStaffFirstName.AsString. + sidsStaffFatherName.AsString; fmData.ShowModal; //Передаем изменения на сервер sidsJobs.ApplyUpdates(0]; sidsFamily.ApplyUpdates(0} ; fmData.Free; end;
Метод SetRange уже рассматривался в главе 3, посвященной локальным базам данных. Он используется для выбора определенного диапазона записей, в соответствии с ограничивающими условиями по ключевому столбцу. В данном случае выбираются строки из таблиц FAMILY и JOBS, у которых в поле EraplD хранится идентификатор текущего сотрудника. Таким образом, при помощи формы fmData организован доступ к клиентским наборам данных sidsJobs и sidsFamily, а изменения, внесенные в эти наборы данных, передаются на сервер только при закрытии формы fmData.
Автоматическая вставка значений в поле EmpID Поле EmpID таблиц JOBS и FAMILY на форме fmData не отображается, однако в нею необходимо каким-то образом вставлять значение идентификатора теI кушего сотрудника. 1. Для этого создадим обработчики событий sidsJobs .OnBeforePost и sidsFamily.OnBeforePost: procedure TdmStaff.sidsJobsBeforePost(DataSet: TDataSet); 3 begin if fmData <> nil then sidsJobsEmpID.AsInteger :— sidsStaffid.AsInteger; end;
procedure TdmStaff.sidsFamilyBeforePost(DataSet: TDataSet); begin if fmData <> nil then sidsFamilyEmpID.Aslnteger := sidsStaffid.AsInteger; end;
Главная форма
265
Условный оператор if fmData <> nil можно прочитать так: "Если форма fmData существует". Таким образом, автоматическое внесение идентификатора сотрудника в поле EmpID будет выполняться только при редактировании данных на форме fmData. ПРИМЕЧАНИЕ Для того чтобы к форме fmData можно было обращаться из модуля данных dmStaf f. укажите в разделе implementation модуля stfDMod ссылку на модуль StfData: implementation uses StfMain, StfData; 2. Сохраните проект и запустите приложение. 3. Выделите в сетке данных dgrstaff добавленного ранее сотрудника, и откройте любым доступным способом окно дополнительных данных. 4. В таблице JOBS должно уже быть несколько записей. Введите какую-нибудь информацию о членах семьи выбранного сотрудника (рис. 7.18) и щелкните мышью на кнопке ОК. 5. Закройте приложение и откройте в конструкторе форм форму fmMain. -Ifflxi
1 /' ' EITiidK Юрии Алексеевич Начапд
[Конец
|Тек.орг|
_*]
01 03 2003 02 03 2003 1
Организаций 1Мо
Должность: Замест|"ель начальница
Приказ о назначении. I1 Приказ об увольнении i
_ г . f
н- | + Сеглья Рздетво] Дата роудбшя|иия Жена
130Z19S1
Людмила
Сын
18102301
ЁЯРЭИ
У.ок
|
Рис. 7.18. Данные о трудовой деятельности и членах семьи
Организация быстрого поиска сотрудников Последнее, что осталось реализовать — это быстрый поиск сотрудников в наборе данных sidsStaff в соответствии с введенным значением в элементе управления edSearch.
266
Глава 7. Приложение "Персонал"
1. Создайте обработчик события edSearch.OnChange, который вызывается при изменении свойства edSearch.Text: procedure TfmMain.edSearchChange(Sender: TObject); var LocField: string[20]; begin if edSearch.Text <> •' then begin case cbSearch.Itemlndex of 0: LocField := 'LastName'; 1: LocField := 'TaxCode'; 2 : LocField := 'TabNum'; 3: LocField := ' T e l ' ; 1 4 : LocField := 'PasspNum ; ' end; dmStaff.sidsStaff.Locate(LocField, edSearch.Text,[loCaselnsensitive]);
end; end;
Поле для поиска определяется на основании выбранного в данный момент элемента раскрывающегося списка cbSearch. Для поиска используется метод Locate. Параметр loCaselnsensitive означает, что поиск в текстовых полях "не чувствителен" к регистру букв. 2. И, наконец, создайте обработчик события cbSearch.OnChange, который вызывается при выборе в раскрывающемся списке Поиск нового элемента: procedure TfmMain.cbSearchChange(Sender: TObject]; begin edSearch.Text := " ; edSearch.SetFocus; end;
3. Теперь можете сохранить проект и откомпилировать приложение в последний раз.
Что дальше? В следующей главе рассматриваются такие вопросы защиты баз данных InterBase как создание новых пользователей и изменение паролей, роли и назначение прав доступа к объектам баз данных. -
-
Глава 8
Организация защиты данных Организовать защиту данных можно на двух уровнях: на уровне приложения и на уровне базы данных. • Защита на уровне приложения подразумевает ограничение доступа пользователя к каким-то элементам пользовательского интерфейса (например, кнопкам или командам меню). При этом доступ к базе данных из других приложений ничем не ограничен, кроме имени пользователя и пароля. • Защита на уровне базы данных подразумевает наложение ограничений на доступ к информации и на ее обработку, основанный на встроенной системе безопасности. При этом вся информация о правах пользователей хранится непосредственно на сервере, и является общей для всех приложений, подключаемых к базе данных. Для более надежной защиты информации предпочтительнее использовать защиту на уровне базы данных, хотя на практике часто используется смешанный способ организации безопасности. Центральная роль в системе организации защиты баз данных InterBase отведена специализированной базе данных isc4.gdb, которая находится в каталоге, в котором установлен SQL-сервер. Она содержит записи для всех пользователей, которым разрешено подключаться к базам данных и службам InterBase на сервере. Эти записи также называют учетными (account). Каждая такая запись содержит имя пользователя и его пароль. Имя пользователя может состоять из 31 символа (регистр букв значения не имеет). Пароль может состоять из восьми символов (регистр учитывается). Перед тем как выполнить любые задачи по обработке и администрированию баз данных, необходимо подключиться к SQL-серверу. При этом каждый пользователь должен ввести свое имя и пароль. Корректность введенных имени и пароля проверяется по базе данных isc4.gdb. Если соответствующая учетная запись найдена,jo выполняется подключение.
Работа с пользователями в программе IBConsole В любом сервере InterBase зарегистрирован пользователь SYSDBA, который по умолчанию имеет пароль rnasterkey. Этому пользователю соответствует специальная учетная запись, которая позволяет игнорировать систему защиты SQLсервера и выполнять любые задачи администрирования. После установки InterBase пользователь SYSDBA является единственным, и он должен зарегистри-
268
Глава 8. Организация защиты данных
ровать всех остальных пользователей сервера. Добавлять, удалять или модифицировать записи базы данных isc4.gdb может только пользователь SYSDBA. Для этого обычно используется программа IBConsole. СОВЕТ
•
Настоятельно рекомендуется при первой же возможности изменить пароль пользователя SYSDBA при помощи той же программы IBConsole.
Администрирование пользователей InterBase в программе IBConsole осуществляется при помощи диалогового окна User Information (рис. 8.1). Получить доступ к этому диалоговому окну можно только после подключению к серверу InterBase. «
Вопросы подключения к серверу InterBase подробно рассматриваются в разделе "Подключение к серверу" главы 4. ^ User Information Re
U?*rName Password Confirm Paswcud; С plans Intamatoi
Ней
Рис. 8.1. Диалоговое окно User Information Для того чтобы открыть это окно, можно воспользоваться одним из двух способов. 1. Выбрать в древовидной структуре, расположенной вдоль левого края окна IBConsole, зарегистрированный сервер и выполнить команду Server >- User Security. 2. Щелкнуть правой кнопкой мыши в древовидной структуре на зарегистрированном сервере и выполнить команду User Security раскрывшегося контекстного меню.
Регистрация нового пользователя Для того чтобы зарегистрировать на сервере нового пользователя, откройте диалоговое окно User Information и выполните следующие действия. 1. Щелкните мышью на кнопке New (Новый). 2. Введите имя пользователя в поле User Name.
Права доступа
269
3. Введите пароль пользователя в полях Password и Confirm Password (Подтвердить пароль). 4. Можете ввести дополнительную тестовую информацию о фамилии, имени и отчестве пользователя в разделе Optional Information. 5. Щелкните мышью на кнопке Apply (Применить), чтобы добавить учетную запись для нового пользователя в базу данных isc4 . gdb. ПРЕДУПРЕЖДЕНИЕ В InterBase использование пробелов в именах пользователей и паролях не допускается.
Изменение данных о пользователе Для того чтобы изменить информацию о пользователе, выполните следующие действия в окне User Information (см. рис. 8.1). 1. Выберите пользователя в раскрывающемся списке, расположенном в верхней части диалогового окна. 2. Измените содержимое любого поля, кроме User Name. Если будет изменен пароль в поле Password, тогда новый пароль нужно ввести еще раз в поле Confirm Password. 3. Для сохранения изменений щелкните мышью на кнопке Apply. ПРИМЕЧАНИЕ Имя пользователя изменить нельзя. Единственный способ сделать это — удалить пользователя, а затем создать новую запись с другим именем.
Удаление пользователя Для того чтобы удалить информацию о пользователе, выполните следующие действия в окне User Information (см. рис. 8.1). 1. Выберите пользователя в раскрывающемся списке User Name. 2. Щелкните мышью на кнопке Delete. В результате на экране появится запрос на подтверждение операции. Если в ответ на этот запрос щелкнуть мышью на кнопке ОК, то текущий пользователь будет удален. Это пользователь больше не сможет подключиться к базам данных сервера. ПРЕДУПРЕЖДЕНИЕ
Несмотря на то, что пользователь SYSDBA может удалить сам себя, делать это настоятельно не рекомендуется, так как в результате будет невозможно модифицировать информацию о других пользователях. После удаления пользователя SYSDBA для восстановления базы данных isc4 . gdb придется заново переустановить сервер InterBase.
Права доступа Новые пользователи автоматически не получают прав для изменения или даже просмотра информации, хранимой в базе данных — права доступа должны быть предоставлены явно. Пользователь (естественно, кроме SYSDBA) не сможет
270
Глава 8. Организация защиты данных
получить доступ ни к одному из объектов базы данных до тех пор, пока ему не будет на это предоставлены соответствующие права. Права доступа в сервере InterBase назначаются при помоши так называемых ролей (role) и SQL-команды GRANT.
Роли Роль — это именованный набор прав доступа, который в дальнейшем можно назначить любому пользователю базы данных. Для создания роли используется команда CREATE ROLE имя_рали. Для удаления роли используется команда DROP ROLE имя_роли. Роль должна указываться при подключении к базе данных. Пример использования команды CONNECT: 1
CONNECT ' c : \ s t a f f \ b a s e \ s t a f f . g d b USER ' u s e r l ' PASSWORD ' p s s w l ' . ROLB 'rolel'
Для того чтобы указать роль при подключении к базе данных из приложения Delphi 7, используется свойство Params соответствующего компонента. Например, в приложении "Персонал", для того чтобы указать роль необходимо изменить обработчик события bbConenct.OnClick, реализованный в программном модуле stfLogin: procedure ТfmLogin.bbConnectClick{Sender: TObject]; var Reg: TRegistry; begin with dmStaff.sqlcStaff do begin 3 Params.Values['Database'] := laedDatabase.Text; Params.Values['User_Name'] := laedUser.Text; Params.Values['Password'] := laedPassword.Text; Params.Values['SQLDialect'] := '3'; Params. Values['RoleName1] := 'rolel'; end; end;
ПРИМЕЧАНИЕ В случае необходимости имя роли можно указывать при помощи отдельного элемента управления.
Команды GRANT и REVOKE Команда GRANT используется для назначения прав доступа и ролей пользователям, ролям и другим объектам баз данных. Рассмотрим синтаксис этой команды на примерах, применительно к приложению "Персонал".
Права доступа
271
Предположим, что к базе данных s t a f f .gdb должны подключаться пользователи трех категорий в соответствии со следующими правами доступа. 1. Добавление, удаление и редактирован!"' всех данных, включая справочники. Для этой категории можно создать роль !менем FullR: CREATE ROLE FullR. 2. Полные права только для работы с та /щей FAMILY. При работе с таблицей STAFF можно редактировать только данные об адресе (столбцы zip, street, House, Tel). Все остальные данные предназначены только для просмотра. Для этой категории можно создать роль с именем chiefR: CREATE ROLE ChiefR. 3- Все данные можно только просматривать. Для этой категории можно создать роль С Именем ReadR: CREATE ROLE ReadR. К первой категории могут относиться работники отдела кадров, ко второй — начальники подразделений, а к третьей — все остальные сотрудники (рис 8.2). Console
Рис. 8.2. Роли в программе IBConsole Назначим права доступа каждой из этих ролей. 4. Вначале предоставим роли FullR полные права доступа ко всем таблицам и представлению FullList: GRANT GRANT GRANT GRANT GRANT GRANT GRANT
ALL ALL ALL ALL ALL ALL A.LL
ОН ON ON ON ON ON OH
DEPS TO FullR; FAMILY TO FullR; JOBS TO FullR; POSS TO FullR; REGIONS TO FullR; STAFF TO FullR; FullList TO FullR
.
272
Глава 6. Организация защиты данных
5. Затем предоставим роли ChiefR полные права доступа к таблице FAMILY, ограниченные права доступа к таблице STAFF, и право только на просмотр всех остальных таблиц и представления FullList: GRANT GRANT GRANT GRANT GRANT GRANT GRANT
SELECT ON DEPS TO ChiefK; ALL ON FAMILY TO ChiefR; SELECT ON JOBS TO ChiefR; SELECT ON POSS TO C h i e f R ; SELECT ON REGIONS TO C h i e f R ; S E L E C T , U P D A T E f Z i p , S t r e e t , H o u s e , T e l ) ON STAFF TO ChiefR; SELECT ON FullList TO ChiefR
В данном случае для доступа к таблице FAMILY использовалось право полного доступа ALL. Соответствующую команду GRANT можно было бы записать и в расширенной форме: GRANT SELECT,DELETE,INSERT,UPDATE OH FAMILY TO chief
6. Наконец, предоставим роли ReadR право только на просмотр всех таблиц и представления FullList: GRANT SELECT GRANT SELECT GRANT SELECT GRANT SELECT GRANT SELECT GRANT SELECT GRANT SELECT
ON DEPS TO ReadR,ON FAMILY TO ReadR; ON JOBS TO ReadR; ON POSS TO ReadR; ON REGIONS TO ReadR; ON STAFF TO ReadR; ON FullList TO ReadR
__
^_^
7. Предположим, что при помощи программы IBConsole были зарегистрированы следующие пользователи: BOSS -- начальник организации, PD1 и PD2 — работники отдела кадров, CHIEFI и CHIEFS — начальники подразделений, USER1 И USER2 — СОТРУДНИКИ.
8. В этом случае для присвоения этим пользователям соответствующих ролей используются следующие команды: GRANT FullR TO PD1, PD2; GRANT ChiefR TO CHIEF1, CHIEF2; GRAHT__ReadR TO BOSS, OSER1, USER2
9. Кроме того, можно предоставить право на выполнение хранимой процедуры -DeleteEmp ТОЛЬКО роли FullR: GRANT EXECUTE ON PROCEDURE DeleteEmp TO FullR
Команда REVOKE Для того чтобы забрать права доступа у роли, пользователя или объекта базы данных используется команда REVOKE. Например, для того чтобы забрать право
Что дальше?
273
на редактирование таблицы STAFF у роли ChiefR, необходимо выполнить следующую команду: REVOKE UPDftTEfZip,Street,House,Tel) ON STAFF FROM ChiefR
Что дальше? Следующей главой завершается часть книги, посвященная базам данных. В ней рассматриваются некоторые методы оптимизации приложений, предназначенных для работы с базами данных, которые повышают их быстродействие, как на стороне SQL-сервера, так и на стороне приложений.
.
Глава 9
Советы по оптимизации работы с базами данных •
В этой главе представлены некоторые рекомендации по повышению производительности приложений Delphi, предназначенных для работы с базами данных. Методы оптимизации подразделяются на три категории: оптимизация базы данных, оптимизация приложения и оптимизация сети. Вопрос оптимизации работы такого программного обеспечения очень обширен, и потому в этой главе будут рассмотрены только самые общие и простые методы.
Оптимизация баз данных Очень просто разработать приложение, которое хорошо работает на этапе тестирования, но затем замедляется при заполнении базы данных или подключении многих пользователей. Поэтому необходимо приложить все усилия к тому, чтобы на этапе тестирования приложения база данных была заполнена либо реальными данными, либо данными, похожими на них. Многие разработчики этого не делает. Вместо этого они создают структуру базы данных (то есть, таблицы и индексы), заполняют каждую таблицу несколькими записями, разрабатывают приложение, а затем поставляют его пользователям. При таком подходе невозможно полностью отследить все возможные ошибки, так как десять записей из базы данных извлекаются быстро в любом случае. Если же на этапе разработки пробную базу данных заполнить несколькими сотнями тысяч записей, то может выясниться, что на извлечение данных о каком-нибудь заказчике потребуется большой промежуток времени. Такая работа приложения недопустима, и сразу становится ясно, что такое программное обеспечение нуждается в доработке. Если базу данных максимально заполнить еще до разработки самого приложения, то это позволит выявить множество ошибок еще в процессе разработки приложения.
Совет 1. О первичных и внешних ключах Большинство SQL-серверов поддерживают объявление первичных и внешних ключей при описании структуры таблиц. Несмотря на простоту объявления ключей, в очень больших таблицах их лучше не использовать. Под "очень большими таблицами " подразумеваются таблицы, количество строк в которых измеряется тысячами. Вместо первичных ключей для таких таблиц лучше ис-
Оптимизация баз данных
275
пользовать уникальные индексы. В результате при попытке продублировать в таблице значение некоторого уникального поля будет выдано сообщение об исключительной ситуации, в котором вместо закодированного будет использоваться нормальное имя индекса. Кроме того, уникальные индексы, в отличие от первичных ключей, можно временно отключать и перестраивать. Со внешними ключами дело обстоит несколько по-другому. В таблице STAFF есть поле zip, значения для которого выбираются строго из таблицы REGIONS. Для этого создается внешний ключ, при объявлении которого InterBase создает индекс по полю Zip. Проблема заключается в том, что этот индекс может значительно понизить производительность. Если предположить, что таблица STAFF будет состоять из сотен тысяч записей, и в ней будут встречаться ссылки всего на 10 регионов (хотя, конечно же, такое предположение для такого приложения как "Персонал" является чисто теоретическим), то в среднем на каждый регион в индексе по полю Zip будет приходиться число записей, измеряемое десятками тысяч. Такие индексы использовать очень нежелательно, так как при их выборе оптимизатором InterBase могут возникнуть проблемы. НЕМНОГО ТЕОРИИ Все SQL-серверы выполняют оптимизацию запросов, передаваемых им клиентскими приложениями. Они анализируют команды SQL и пытаются выбрать наиболее эффективный способ их выполнения. Средство, выполняющее этот анализ и выбор, и называется оптимизатором запросов. При разработке плана выполнения запроса оптимизатор учитывает такие факторы как доступные индексы, объем дискового пространства, которое должно быть прочитано, и т.п.
Совет 2. О хранимых процедурах В хранимых процедурах InterBase можно использовать операторы SELECT. Это позволяет просматривать главную таблицу и производить поиск данных в необходимых полях второстепенной таблицы. Кроме того, хранимые процедуры InterBase можно вызывать в триггерах для автоматического обновления данных. При помощи хранимых процедур можно создавать сложные наборы данных, которые не могут быть получены при помощи обычных SQL-операторов. Кроме того, результирующий набор данных формируется гораздо быстрее, чем при использовании обычных SQL-операторов, так как: выборка происходит непосредственно на сервере.
Совет 3. О триггерах Для организации взаимодействия между таблицами используйте не хранимые процедуры, а триггеры. В случае использования хранимых процедур всегда существует вероятность того, что с их помощью кто-то из пользователей базы данных может добавить свои собственные записи. По этой причине команды вставки, обновления и удаления записей лучше реализовывать в триггерах, таблиц.
276
Глава 9. Советы по оптимизации работы с базами данных
Совет 4. Об индексах При индексации по нескольким полям SQL-сервер может оптимизировать поиск информации только в тех полях, которые указаны в индексе первыми. Например, в базе данных staff .gdb есть индекс INDEX1 по полям Lastname, FirstName и FatherName таблицы STAFF. Если бы в приложении "Персонал" использовался оператор SQL вида SELECT FatherName, то индекс INDEX1 при оптимизации не использовался бы. Поэтому для оптимизации запросов лучше использовать индексы по отдельным полям. Не создавайте два индекса, которые начинаются с одного и того же поля, так как это мешает при выполнении оптимизации запросов. Кроме того, не создавайте слишком много индексов, особенно по нескольким полям, так как это может не ускорить, а наоборот — замедлить обработку запросов. Для небольших таблиц достаточно ограничиться только одним первичным ключом или уникальным индексом или вообще не создавать никаких индексов.
Совет 5. О полях типа varchar В базах данных InterBase способ хранения значений типа char и типа varchar практически ничем не отличается. Внешне значения типа char передаются в Delphi-приложения дополненными пробелами до длины поля, а значения типа varchar не имеют хвостовых пробелов. Тем не менее, значения обоих типов передаются по сети вместе с хвостовыми пробелами. При разработке базы данных помните о том, что как значения типа char, так и значения типа varchar передаются от сервера к клиенту дополненные пробелами до указанной длины. Если необходимо использовать большую строку переменной длины, то лучше применять тип BLOB.
Совет 6. О размере страниц в базе данных По умолчанию в InterBase 6.5 размер страницы равен 1024 байтов, что является слишком маленьким значением. В данном случае экономить память не надо! Размер страницы можно увеличить до 2048 или 4096 байтов, а для систем с большими таблицами — даже до 8192 байтов. Увеличение размера страницы позволяет хранить на одной странице больше BLOB-значений и записей. Все это повышает быстродействие, особенно при использовании индексов в> больших таблицах.
Оптимизация приложений Оптимизация приложений, в основном связана с использованием компонентов, предназначенных для доступа к данным. Однако, в этом разделе будут также затронуты некоторые вопросы, связанные с правилами построения SQLоператоров.
Оптимизация приложений
277
Совет 1. О транзакциях Когда речь идет о транзакции, важно знать, как она используется. Если какая-то транзакция остается открытой в течение всего дня, и в ней не выполняется никаких действий, то это не приведет к каким-либо серьезным последствиям. Если же в течение такой транзакции ежеминутно запускаются запросы, то это приведет к перерасходу оперативной памяти сервера. Таким образом, долго открытые транзакции могут привести к значительному снижению производительности сервера.
Совет 2. О команде LIKE Команду LIKE необходимо использовать очень осторожно. Для таблицы STAFF был создан индекс по полям LastName, FirstName и FatherName. Этот индекс будет использоваться сервером InterBase для оптимизации поиска в случае выполнения следующего SQL-оператора: SELECT * FROM STAFF WHERE LastHame LIKE 'Иван%';
Теперь рассмотрим еще один SQL-оператор: SELECT * FROM STfiFF WHERE LastName LIKE '%ван%' ;
При выполнении такого запроса индекс использоваться не будет, что приведет к замедлению работы сервера. Результирующий набор данных для подобных SQL-запросов формируется только посредством последовательного просмотра таблиц. Это означает, что сервер InterBase поочередно извлекает все записи таблицы с жесткого диска, и проверяет каждую из них на наличие в фамилии сотрудника подстроки "ван". На последовательный просмотр таблиц уходит много времени, поэтому его следует избегать. То же самое относится к использованию функций в запросах с командой LIKE. InterBase не может определить результат, возвращаемый функцией (например, функцией UPPER), поэтому выбирает самый низкий уровень оптимизации: SELECT_* FROM STAFF WHERE LastName LIKE UPPER('Иванов' j
Если в запросе необходимо использовать поиск, не зависящий от регистра символов, то лучше вставить в таблицу еще одно поле, предназначенное для хранения содержимого соответствующего поля в верхнем регистре. При этом преобразование исходного значения в строку в верхнем регистре можно выполнять в триггере в момент изменения данных.
Совет 3. О подготовке запросов Каждый SQL-оператор, который передается на сервер InterBase, подготавливается. Подготовка запросов очень важна, особенно для низкоскоростных линий передачи и Internet. Например, если к какой-то базе данных осуществляется подключение через линию со скоростью обмена 28.8, то на обработку первого
278
Глава 9. Советы по оптимизации работы с базами данных
запроса (который подготавливается) уйдет примерно в семь раз больше времени, чем на обработку следующих аналогичных запросов. Например, в приложении "Персонал" используется компонент sglqChildDeptsList типа TSQLQuery, который применяется для извлечения данных из таблицы DEPS при помощи вызова хранимой процедуры ChildDeptsList. У компонентов класса TSQLQuery для подготовки запросов используется метод PrepareStatement.
Совет 4. Об извлечении записей При работе с большими наборами данных, записи с сервера можно извлекать не все сразу, а постепенно. Если база данных s t a f f .gdb расположена на удаленном сервере, а таблица STAFF, например, состоит из 100000 записей, то выполнение SQL-оператора SELECT * FROM STAFF может привести к перегрузке сети. У класса TSimpleDataSet есть свойство PacketRecords, которое по умолчанию имеет значение -1. Это свидетельствует о том, что при открытии набора данных извлекаются сразу все записи. При работе с большими наборами данных это приведет к большим временным задержкам. По этой причине для свойства PacketRecords лучше указать какое-нибудь небольшое значение. В результате с сервера будут изначаньно извлечено только указанное количество записей, а остальные будут подгружаться по мере перемещения по клиентскому набору данных.
Совет 5. Об удаленном подключении Есть только два вида подключений: локальное и удаленное. Путь доступа к базе данных при локальном подключении выглядит следующим образом: е:\programs\staff\base\staff.gdb
При локальном подключении для обмена данными между приложением и сервером используются файлы, отображенные в оперативной памяти, работа с которыми выполняется очень быстро. Обмен данными при таком подключении происходит гораздо быстрее, чем при удаленном подключении. Путь доступа к базе данных при удаленном подключении выглядит следующим образом: localhost:\path\staff.gdb
или myserver:е:\path\staff.gdb
При удаленном подключении доступ к серверу InterBase происходит через сеть. Учитывая это, при разработке приложений лучше использовать удаленное подключение, так как в этом случае будут выявлены те проблемы, связанные с понижением производительности, которые не могут быть обнаружены при использовании локального подключения.
Оптимизация работы сети
279
Совет 6. О метаданных Метаданные — это информация о структуре элементов базы данных. При разработке приложения они извлекаются с сервера для организации работы с полями, индексами и т.п. После окончания разработки приложения их извлечение можно отключить. Для этого присвойте свойству NoMetaData компонентов, предназначенных для работы с наборами данных, значение True- Например, в приложении "Персонал" такими компонентами являются sqlgChildDeptsList и sqldsDeleteEmp. В результате метаданные не будут извлекаться с сервера каждый раз при открытии набора данных. Это значительно повысит скорость обработки операторов SELECT.
Совет 7. Об объектах полей Вместо использования метода FieldByName или свойства Fields объектов, предназначенных для работы с наборами данных, рекомендуется создавать статические объекты полей. Процесс создания этих объектов при помощи редактора полей компонентов sirnpleDataSet рассматривался при разработке приложения "Персонал". Объекты полей более надежные, безопасные и устойчивые к изменениям в базе данных.
Оптимизация работы сети Ниже перечислены некоторые советы по оптимальной конфигурации подключений к сетевым базам данных. • Вполне очевидный совет: используйте более скоростные сети и сетевые платы. • Если сетевой протокол поддерживает пересылку пакетов больших размеров, то можно попытаться увеличить размер пакетов для уменьшения их общего количества. Чем меньше пакетов передается по сравнительно медленным глобальным сетям, тем лучше. • Второстепенные формы с элементами доступа к наборам данным (имеются в виду не клиентские наборы данных) рекомендуется создавать не при инициализации приложения, а динамически, так как каждое открытое подключение к; серверу забирает часть пропускной способности сети. • При разработке приложений в глобальных сетях оценивайте реальную необходимость использования компонентов DBGrid и DBCtrlGrid. Сетки данных прекрасно подходят для работы в локальных сетях, но они часто содержат намного больше данных, чем необходимо для работы приложения. По этой причине снижается пропускная способность сети и замедляется работа приложения. • Свойству KeepConnection компонента SQLConnection рекомендуется присвоить значение False. Хотя это и замедляет работу приложения из-за необходимости каждый раз заново подключаться к серверу, несомненным преимуществом является повышение пропускной способности для других уда-
280
Глава 9. Советы по оптимизации работы с базами данных
ленных пользователей. Для глобальных же сетей это является очень важным фактором. Кроме того, при таком подходе уменьшается количество подключений к серверу. • Если клиентские приложения используют для связи с сервером базы данных протокол TCP/IP, то правильная настройка этого протокола может существенно улучшить производительность системы в целом.
Что дальше? В этой главе были рассмотрены методы оптимизации приложений, баз данных и сети. Некоторые советы имели отношение к базам данных InterBase, но некоторые из них могут быть использованы при работе и с другими SQLсерверами. Эта глава является заключительной в части, посвященной базам данных. В следующей части будут рассмотрены вопросы организации печати и построения отчетов в приложениях Delphi 7.
ЧАСТЬ
in
ПЕЧАТЬ и ОТЧЕТЫ в DELPHI 7
Глава 10
Печать в приложениях Delphi .
ях • • • •
В этой главе будут рассмотрены четыре способа печати данных в приложениDelphi. Печать форм. Печать содержимого компонента RichEdit. Печать в текстовом режиме. Печать в графическом режиме.
Все способы печати рассмотрены на примере приложении "Персонал", разработанном в предыдущей части книги, кроме одного способа печати -- печати содержимого компонента RichEdit, для демонстрации работы которого в этой главе будет создано отдельное приложение.
Печать форм Вероятно, самым простым способом организации печати в приложениях Delphi является печать форм при помощи метода Print. Для того чтобы увидеть это на конкретном примере, внесем некоторые дополнения в приложение "Персонал". 1. Расположите в модуле данных компонент PrintDialog с вкладки Dialogs пачитры компонентов и присвойте ему имя pd_l. Этот невизуальный компонент используется для отображения стандартного окна печати Windows. На той же вкладке Dialogs есть еще один компонент, который можно использовать при организации печати -- PrinterSetupDialog. Этот компонент используется для отображения окна настройки параметров принтера. В данном примере он использоваться не будет, так как настройку принтера можно произвести, щелкнув мышью на кнопке Options (Свойства) в диалоговом окне печати. 2. Перейдите в конструкторе форм к форме fmNew и расположите на ней еще один компонент BitBtn. 3. Присвойте его свойству Name значение bbPrint, а свойству Caption — значение Печать. 4. Назначьте для свойства Glyph какую-нибудь подходящую пиктограмму, например, с изображением принтера. Расположите новую кнопку справа от кнопки bbPhoto.
Печать форм
283
Затем расположите на форме fmNew компонент Label с вкладки Standard палитры компонентов, и присвойте ее свойствам значения в соответствии с табл. 10.1. Таблица 10.1. Свойства компонента Label, расположенного на форме fmNew Свойство
Значение
Описание
Align
alTop
Располагается вдоль верхнего края свободного пространства формы fmNew.
Alignment
taCenter
Надпись выровнена по центру.
Font. Size
14
Размер шрифта.
Наше
laDate
Префикс la — сокращение от "Label", слово "date" переводится как "дата".
Visible
False
При открытии формы метка невидима.
Компонент laDate будет использоваться для отображения текущей даты при печати. Теперь форма fmNew в режиме конструктора должна выглядеть примерно так, как показано на рис. 10.1.
НН^НЩМНШВВЖЖ
1 J-. fmMew
-loixi
Имя
Г
Отчество Подразделение Должность
f,4
|
Т абельньй номер
^
- - . - ' •
| > ' . ~ .
Зарплата
!-,
И,.-
,
-„^;ч,,,,
Место рождения
!
ИдентиФ-нкаинонньй код ' Почтовый nnne«t Улица
- Г
Телефон
ii
Номер паспорта Дата выоачи паспорта
..:--:-.:-.-.!
f fit
t:::;:::::
i i-- 1
''
Номер дома
* . :
i ::;
' '"
' .
jaDaie
Рис. 10.1. Форма fmNew после размещения на ней двух новых компонентов При выводе формы на печать учитываются все расположенные на ней компоненты. С учетом этого факта, те компоненты, которые не должны отображаться при печати, необходимо скрыть перед вызовом метода Print, а затем опять отобразить их после завершения выполнения метода. Для этого создадим отдельную процедуру. 5. Объявите в модуле stfNew.pas закрытую процедуру VisibilityOfButtons: private
{ Private declarations }
284
Глава 10. Печать в приложениях Delphi
procedure VisibilityOfButtons(IgVisible: boolean); 6. В разделе implementation создайте реализацию этой процедуры: procedure T f m N e w . V i s i b i l i t y O f B u t t o n s ( I s V i s i b l e : var i: integer; begin
boolean);
for i := 0 to ComponentCount - 1 do if Components[i] is TBitBtn then TBitBtn(Components[i]).Visible := IsVisible; l a D a t e . V i s i b l e := not IsVisible; laDate. Caption := 'Данные на ' +• D a t e T o S t r ( N o w ) ; end;
Для сокрытия или отображения компонентов можно было бы просто использовать четыре раза оператор присваивания вида BitBtn. v i s i b l e := IsVisible, однако в данном примере применен циклический перебор компонентов формы. Общее количество компонентов, размещенных на форме, содержится В свойстве TForm.ComponentCount. Свойство TForm. Components — это массив объектов типа TComponent, содержащий ссылки на все компоненты формы. При помоши оператора if Components[i]
is TBitBtn
проверяется, является ли текущий компонент объектом класса TBitBtn. Если это так, тогда свойству Visible соответствующего компонента присваивается значение параметра IsVisible. Обратите внимание на то, что здесь выполняется приведение типа TComponent к типу TBitBtn. « Приведение типов рассматривается в разделе ков событий и создание первого приложения" главы 1.
"Практикум:
создание
обработчи-
В завершение процедуры VisibilityOfButtons определяется видимость компонента laDate и его надпись. 7. Создайте обработчик события bbPrint.OnClick и вставьте в него следующие строки программного кода: if d m S t a f f . p d _ l . E x e c u t e - t h e n begin VisibilityOfButtons(False); Print;
VisibilityOfButtons(True); end;
Если в диалоговом окне печати была нажата кнопка ОК, то перед выводом формы на печать скрываются кнопки BitBtn, и отображается текущая дата при
Печать содержимого компонента RichEdit
285
помощи компонента laDate. После вывода содержимого формы на печать кнопки BitBtn опять отображаются, а компонент, laDate становится невидимым. S. Сохраните проект и запустите приложение. 9. Откройте форму обших данных для какого-нибудь сотрудника и щелкните мышью на кнопке Печать. 10. В диалоговом окне печати выберите принтер и настройте его параметры 11. Щелкните мышью на кнопке ОК. Не забудьте предварительно включить принтер и заправить в него бумагу.
Печать содержимого компонента RichEdit Компонент RichEdit находится в палитре компонентов на вкладке Win32. Он предназначен для ввода и форматирования текста. По своей сути он напоминает стандартный компонент Memo, однако его отличие заключается в возможности изменения параметров шрифта и абзаца различных частей текста. 1. Создайте новый проект. Поскольку это приложение -- исключительно демонстративное, не будем придерживаться соглашений об именах. 2. Расположите на форме компонент RichEdit и присвойте его свойству Align значение alTop. 3. Также расположите на форме три кнопки BitBtn. Свойству Kind одной из кнопок присвойте значение bkciose, а свойству Caption — значение Выход. Свойству Caption остальных двух кнопок присвойте значения Открыть и Печать. 4. В завершение расположите на форме два компонента с вкладки Dialogs палитры компонентов: OpenDialog и PrintDialog. 5. Свойству OpenDialogl. Filter присвойте значение Файлы RTF i * . r t f . RichEdnl Формат файлов RTF (Rich Text Format) поддерживается большинством текстовых процессоров, включая Microsoft WordPad и Word. Форма в режиме конструктора должна Рис. 10.2. Форма с компонентом выглядеть примерно так, как показано на RichEdit в режиме конструктора рис. 10.2. 6. Создайте обработчик события onclick для кнопки Открыть и введите в него следующий программный код: if OpenDialogl.Execute then RichEditl.Lines.LoadFrornFile(OpenDialogl.FileNamel; Здесь в компонент RichEdit загружается содержимое выбранного файла -.rtf.
286
Глава 10. Печать в приложениях Delphi
7. Создайте обработчик события onclick кнопки Печать и вставьте в него следующий программный код: if PrintDialogl.Execute then RichEditl.Print('' Параметр, передаваемый в метод TRichEdit.Print, — это строка, которая отображается программами управления печатью. 8. Запустите приложение. , 9. При запросе на сохранение исходных файлов присвойте программному модулю и файлу проекта любые имена. 10. Щелкните мышью на кнопке Открыть и выберите какой-нибудь файл *. r t f . 11. Затем щелкните мышью на кнопке Печать и выведите содержимое компонента RichEdit на печать.
Печать в текстовом режиме При печати в текстовом режиме вывод на принтер осуществляется построчно, как при выводе в обычный текстовый файл. Для этого используются процедуры AssignPrn, Rewrite, Write, WriteLn И System.CloseFile. 1. Откройте в Delphi проект S t a f f . d p r и перейдите в конструкторе форм к форме fmMain. 2. Расположите на панели инструментов tlbMain кнопку BitBtn и присвойте ее свойству Name значение bbPrint. Свойству Caption этой кнопки присвойте значение Реестр, а свойству Visible — значение False. 3. Назначьте свойству Glyph какую-нибудь подходящую пиктограмму. 4. Перейдите в исходном тексте модуля stfMain.pas к обработчику события tvStructure.OnChange и вставьте в него следующую строку, выделенную жирным шрифтом: procedure T f m M a i n . t v S t r u c t u r e C h a n g e ( S e n d e r :
5. В разделе uses модуля StfMain.pas вставьте ссылку на два модуля: Printers и StrUtils. В модуле Printers реализован класс TPrinter, который используется для определения параметров печати. В модуле StrUtils реализованы функции и процедуры для работы со строками. 6. Создайте обработчик события bbPrint.Onclick:
Печать в текстовом режиме procedure TfmMain.bbPrintClick(Sender: TObject); var F: TextFile; begin if dmStaff.pd_l.Execute then begin Printer.Orientation := poLandScape; AssignPrn(F); Rewrite(F); //Печать заголовка 1 Printer.Canvas.Font.Name := 'Courier New ; Printer.Canvas.Font.Size := 12; Printer.Canvas.Font.Style := [fsBold]; WriteLn(F,'Реестр сотрудников на ' + DateToStr(Now)); WriteLn(F,'Подраэделение: ' + tvStructure.Selected.Text); Printer.Canvas.Font.Style := [ ] ; WriteLn(F,DupeString{'-' ,100) ) ; WriteLn(F,' Фамилия I' + 1 Имя |' + ' Отчество !' + ' Подразд . | Должность ') ; WriteLnfFjDupeStringC-1 ,100)) ; //Печать данных with dmStaff, sidsStaff do begin First; while not EOF do begin Write(F,PadStr(sidsStaffLastName.AsString,25) + '|'); Write(F,PadStr(sidsStaffFirstName.AsString,20) + '!'); Write{F,PadStr(sidsStaffFatherName.AsString,20) + M') Write(F,PadStr(sidsStaffDeptShortName.AsString,10) + ' WritelJi(F,PadStr(sidsStaffPosShortName.AsString,20)); Next; end; WriteLn(F,DupeString('-',100)); WriteLnfF,'Всего: ' + IntToStr(RecordCount)); System.CloseFile(F); end; end; end;
287
288
Глава 10. Печать в приложениях Delphi
7. В этом методе используется пользовательская функция PadStr, которую необходимо создать в исходном тексте модуля StfMain.pas перед реализацией метода bbPrintciick: function PadStr(s:string; Len: integer): string; begin Result := s + DupeString('
',Len - L e n g t h ( s ) } ;
end;
Эта функция возвращает строку s, дополненную пробелами до длины, указанной в параметре Len. Функция DupeString возвращает строку, указанную в качестве первого параметра (в данном случае — пробел), продублированную столько раз, сколько указано во втором параметре функции. Рассмотрим подробнее метод bbPrintciick. Объект Printer — это экземпляр класса TPrinter. При помощи свойств этого объекта можно определить такие параметры печати, как ориентация бумаги и шрифт. Ориентация бумаги задается при помощи свойства Printer.Orientation. Это свойство может иметь одно из двух значений: poLandScape (альбомная ориентация) или poPortrait (книжная ориентация). Вывод на печать начинается с вызова процедур AssignPrn и Rewrite, которым в качестве параметра передается переменная типа TextFile. Для завершения печати вызывается процедура System. CloseFile. Шрифт печати определяется при помощи свойства Printer.Canvas.Font. В данном случае используется шрифт Courier Hew размером 12. Печать заголовка осуществляется полужирным шрифтом — свойство: Printer.Canvas.Font.Style = [fsBold].
Вывод на печать осуществляется построчно при помощи процедуры WriteLn. Процедура Write отличается от процедуры WriteLn тем, что она не выполняет переноса данных на следующую строку. 8. Сохраните проект и запустите приложение. 9. Выберите какое-нибудь подразделение и распечатайте данные о сотрудниках этого подразделения, щелкнув мышью на кнопке Реестр.
Печать в графическом режиме Печать в графическом режиме полностью основана на использовании объекта Printer и его свойства Canvas. Такой способ печати более гибкий, так как позволяет выводить текст в различных позициях на странице, а также применять различные графические элементы. Реализуем в приложении "Персонал" печать данных о трудовой деятельности в графическом режиме. 1. Откройте в конструкторе форм форму fmData. 2. Расположите компонент Button в правом нижнем углу панели paJobs.
Печать в графическом режиме
289
3. Присвойте свойству Name этого компонента значение btPrint, а свойству Caption — значение Печать. 4. Вставьте в разделе uses программного модуля stfData ссылку на модуль Printers. 5. Создайте обработчик события btPrint.Onclick и вставьте в него следующий программный код: with dmStaff, Printer do . if pd_l.Execute then begin Orientation := poPortrait; BeginDQc; Canvas.Brush.Style := bsClear; Canvas.Rectangle(100,100,PageWidth,300) ; Canvas.Font.Style := [fsBold]; Canvas. Font. Name := 'MS Sans Serif; Canvas.Font.Size := 12; Canvas.TextOut(110,105,sidsStaffLastName.AsString); Canvas.TextOut(110,155,sidsStaffFirstName.AsString); Canvas.TextOut(110,205,sidsStaffFatherName.AsString); Canvas.TextOut(1500,105,'Трудовая деятельность1); Canvas.Font.Size := 10; Canvas.Font.Style := [] ; with sidsJobs do begin First; Canvas.Rectangle(100,300, 400,350); Canvas.TextOut(110,305,'Начало'); Canvas.Rectangle(400,300,700,350); Canvas.TextOut(410,305,'Конец1); Canvas.Rectangle(700,300,PageWidth, 350) ; Canvas.TextOut1710,305,'Организация, подразделение, должность'); while not EOF do begin if (500+ (RecNo-l)*50) > PageHeight then NewPage; Canvas.Rectangle(100,350+(RecNo-1)*150, PageWidth,500+(RecNo-1)*150); Canvas.TextOut(110,355+(RecNo-1)*150, DateToStr(sidsJobsStartDate.AsDateTime)); Canvas.TextOut(410,355+(RecNo-1)*150, DateToStr(sidsJobsStopDate-fisDateTime)); Canvas.TextOut(710,355+(RecNo-1)*150,
290
Глава 10, Печать в приложениях Delphi
sidsJobsOrganization.AsString]; Canvas.TextOut(710,405+(RecNo-1)*150, sidsJobsDep.AsString); Canvas.TextOut(710, 455+(RecNo-1)*150, sidsJobsPos.AsString); Next ; end; end; EndDoc; end; Как видите, печать в графическом режиме начинается с вызова метода Printer .BeginDoc, а завершается вызовом метода Printer .EndDoc. Все, что рисуется на "холсте" (слово "canvas" переводится как "холст") между вызовами этих двух методов выводится на принтер. . В данном случае было использовано только два метода рисования: Printer.Canvas.TextOut И Printer.Canvas.Rectangle. Первый ИЗ НИХ выводит текст в указанной позиции на странице. При этом параметры шрифта определяются свойством Printer. Canvas . Font. Метод Rectangle рисует на странице прямоугольник с заданными координатами верхнего левого и правого нижнего углов. Все координаты указываются в пикселях, относительно верхнего левого угла страницы. Свойства Printer. PageWidth и Printer. PageHeight содержат ширину и высоту страницы в пикселях, установленную для.текущего принтера. Эти значения зависят от выбранного разрешения печати, которое выражается в единицах dpi (dots per inch — точки на дюйм). Чем выше значение dpi, выбранное для текущего принтера, тем больше будут значения свойств PageWidth и PageHeight. Метод Printer .NewPage используется для перехода на новую страницу. Таким образом, в графическом режиме печати можно самостоятельно управлять постраничным выводом на печать. 6. Сохраните проект и запустите приложение. 7. Откройте форму дополнительных данных для какого-нибудь сотрудника и напечатайте информацию о его трудовой деятельности, щелкнув мышью на кнопке Печать.
Что дальше? В следующей главе рассмотрены компоненты Rave Reports, предназначенные для разработки отчетов.
Глава 11
Отчеты Rave Reports Существует два способа формирования отчетов. • Первый способ основан на применении методик вывода на печать, описанных в предыдущей главе. Однако, такой подход может потребовать от разработчика достаточно кропотливого труда, особенно в случае печати отчетов в графическом режиме. • Второй способ заключается в использовании специализированных компонентов. В Delphi 7 (выпуски Professional и Enterprise) появились новые компоненты Rave Reports компании Nevrona, При помощи этих компонентов можно без труда создавать как простые табличные, так и сложные отчеты.
Создание отчета для приложения "Персонал" Рассмотрим пример создания простого отчета для приложения "Персонал", созданного в части 2, который будет отображать количество сотрудников в каждом из подразделений. 1. Откройте в конструкторе форм модуль данных dmStaf f и расположите в нем компонент SimpleDataSet с вкладки dbExpress 2. Присвойте свойству Name этого компонента имя sidsReport, а для свойства Connection укажите ссылку на компонент sqlqStaf f. 3. В инспекторе объектов дважды щелкните мышью в поле свойства sidsReport.DataSet-CommandText и в открывшемся окне CommandText Editor введите следующий оператор SQL: SELECT DEPS.DeptFullName, COUNT(STAFF,DepID) FROM STAFF INNER JOIN DEPS ON STAFF.DepID = DEPS.DeptID GROUP BY DEPS.DeptFullName
В этом запросе выбираются названия подразделений, и для каждого из них выполняется подсчет количества сотрудников в таблице STAFF. 4. Закройте окно CommandText Editor, щелкнув мышью на кнопке ОК. 5. Расположите в модуле данных компонент Rv Data Set Connect ion с вкладки Rave палитры компонентов и присвойте его свойству Name значение rvdcDeps. Этот компонент используется для связи средств Rave Reports с набором данных, на основании которого формируется отчет. 6. Укажите для свойства rvdcDeps.DataSet ссылку на компонент sidsReport.
292
Глава 11. Отчеты Rave Reports
Перейдем непосредственно к разработке отчета. 7. Выполните в Delphi команду Tools >- Rave Designer. В результате будет запущено специальное приложение Rave Reports, предназначенное для разработки форм отчетов (рис. 11.1). Rave Reports 5.U-P
И5Ы
Name , Orientation
DI»WIB ) BaCote | Slandad j Report | Zoom
CO!OFE | Lnw
• 11.000 8500"
PapaSce
Name prpperty -ThenameoilbecoiTipponenl Th* * naw can artf conlan tfie leffef s .A-Z.Q-U and "he underscore cha«ec3er[J The name muil be De)a Connector
ix-oiv Рис. 11.1. Средство Rave Reports
8. В этом приложении выполните команду File >- New Data Object, чтобы создать J.I,. ..• новый объект данных. В результате на ^ Data Loctup Security Corttder , fig Database СоппеПюп экране появится окно Data Connections ~5 Direct Data View (рис. 11.2). Simple Secu' 9. Выберите в списке Data Object Type (Тип объекта данных) элемент Direct Data View (Прямое представление данных) и щелкните мышью на кнопке Next. 10. В списке Active Data Connections (Активные подключения к данным) выберите элемент rvdcDeps и щелкните мышью на Рис. 11.2. Окно Data Connections кнопке Finish. 11. В древовидной структуре проекта, расположенной в правой части окна Rave Reports, разверните сначала элемент Data View Dictionary (Словарь представлений данных), а затем — элемент DataViewl. Как видно на рис. 11.3, у элемента DataViewl есть два подчиненных элемента, которые соответствуют полям набора данных sidsReport.
Создание отчета для приложения "Персонал"
293
12. Выполните команду Tools >- Report Wizards >- Simple Table, чтобы запустить мастер простых табличных отчетов (рис. 11.4) Wz
Selecl The Doli View you wi«h to им for Th*t report
' atalog В ^DalaViewDiclionarv В О DataVmnl
Carcef
Рис. 11.3. Поля набора данных sidsReport
Рис. 11.4. Мастер простых табличных отчетов
13. В первом окне мастера выберите в списке элемент DataViewl и щелкните мышью на кнопке Next. 14. Во втором окне мастера щелкните мышью сначала на кнопке АИ, чтобы выделить все поля в списке, а затем — на кнопке Next. 15. В третьем окне просто щелкните мышью на кнопке Next. 16. В четвертом окне введите заголовок отчета в поле Report Title, например, "Распределение сотрудников по отделам", а также укажите размер отступов от края страницы и щелкните мышью на кнопке Next. 17. В последнем окне мастера щелкните мышью на каждой из трех кнопок Change Font и выберите какой-нибудь кириллический шрифт. 18. В завершение щелкните мышью на кнопке Generate, в результате чего будет сформирован макет отчета (рис. 11.5). 19. Щелкните мышью на заголовке поля DEPTFULLNAME (средняя строка в конструкторе отчета). В результате этот элемент отчета будет выделен зелеными маркерами. 20. Присвойте свойству Text этого объекта значение Подразделение. 21. Также выделите заголовок поля COUNT, и укажите для его свойства Text значение Сотрудников, а для свойства L e f t — значение 5. 22. Уменьшите значение свойства Left для объекта данных поля COUNT — нижняя строка в конструкторе отчета. 23. Присвойте свойству width объекта данных поля DEPTFULLNAME значение 4,9.
294
Глава 11. Отчеты Rave Reports Evert Е*»
Т DMaVew! Regrai DalaVewl TUeBenQ
Распределение сотрудников по отделам
ф MaVfewl Begnit- Dmyewl QjtiBano
Рис. 11.5. Макет отчета, сформированный мастером простых табличных отчетов Макет готового отчета представлен на рис. 11.6.
Распределение сотрудников по отделам
Рис. 11.6. Откорректированный макет отчета 24. Выполните команду File >- Save As и сохраните этот отчет в каталоге, в котором хранится приложение "Персонал", в файле под названием Deps.rav. 25. Переключитесь в интегрированную среду разработки Delphi 1', расположите в модуле данных компонент rvProject с вкладки Rave палитры компонентов и присвойте ему имя rvpDeps.
Особенности интерфейса программы Rave Reports
295
26. Б инспекторе объектов дважды щелкните мышью в поле свойства rvpDeps .ProjectFile и выберите в открывшемся диалоговом окне файл Deps. rav. 27. Перейдите в конструкторе форм к форме fmMain и расположите на панели инструментов tlbMain между кнопками bbExit и bbPrint кнопку BitBtn. 28. Присвойте ее свойству Name значение bbReport, а свойству Caption — значение Отчет. Можете также назначить подходящую пиктограмму свойству Glyph. 29. Создайте обработчик события bbReport.Onclick и вставьте в него следующую строку: dmStaff.rvpDeps.Execute; 30. Присвойте свойству sqlcStaff .Connected значение False и сохраните проект. 31. Запустите приложение. Щелкните мышью на кнопке Отчет. 32. В результате откроется диалоговое окно Output Options (Параметры вывода), показанное на рис. 11.7. В этом окне можно выбрать один из следующих параметров, определяющих последующий вывод отчета: распечатать отчет (переключатель Printer), выполнить предварительный просмотр отчета (переключатель Preview) или сохранить отчет в файл для дальнейшей распечатки (переключатель File).
rSetocMMrier Report Destitution -
OK
^ г е**сг
Cancel Setup
Г •£=Г
r-
Рис. 11.7. Диалоговое окно Output Options
33. Выберите переключатель Preview и щелкните мышью на кнопке ОК. В результате откроется окно предварительного просмотра отчета, из которого этот отчет можно затем распечатать или сохранить на диске (рис. 11.8). На этом разработка приложения "Персонал" завершена. El Полные листинги исходного кода (без комментариев) представлены на прилагаемой к книге дискете.
В завершение же этой главы кратко рассмотрим особенности программы Rave Reports.
Особенности интерфейса программы Rave Reports Интерфейс программы Rave Reports достаточно нагляден и интуитивно понятен. Для того чтобы запустить эту программу, выполните одно из следующих действий.
Распределение сотрудников по отделам Подразделение Руководство
Сотрцдников 1
\ £
l i , '
- . .
Рис. 11.8. Предварительный просмотр отчета Дважды щелкните мышью на компоненте rvProject:. Выполните команду Rave Visual Designer контекстного меню, раскрываемого щелчком правой кнопкой мыши на компоненте rvProject. Интерфейс Rave Visual Designer состоит из следующих составных частей. Строка меню. Набор инструментов, соответствующих командам меню. Набор инструментов, для работы с макетом отчета. Панель вкладок, на которых элементы макета отчета и инструменты распределены по категориям. Панель редактора свойств, очень напоминающая инспектор объектов Delphi. Конструктор макета отчета. Древовидная структура проекта. На рис. 11.9 представлена панель вкладок. Dtanng [ ВяСо* | Slsnded | Repot | Zoom j Colors I lines
Forts 1 Ahgnmei*
Рис. 11.9. Панель вкладок программы Rave Reports На панели вкладок элементы макета отчета и инструменты распределены по следующим категориям. • Drawing — графические элементы — наподобие линий и прямоугольников. • Bar Code — элементы для создания штрих-кодов. • Standard — стандартные элементы: текстовое поле, memo-поле, графическое изображение и т.п.
Резюме • • • • • • •
297
Report — элементы, предназначенные для отображения и обработки данных. Zoom — инструменты масштабирования макета. Colors — палитра для выбора цвета текущего элемента. Lines — выбор типов линий. Fills — выбор типов заполнения. Fonts — настройка параметров шрифта. Alignment — инструменты выравнивания элементов макета.
Если выполнить единственную команду Undock контекстного меню, раскрываемого щелчком правой кнопкой мыши на любой вкладке, то эта вкладка превратится в обычную панель инструментов. Для выполнения обратной операции необходимо выполнить команду Dock контекстного меню, раскрываемого щелчком правой кнопкой мыши на панели инструментов. Таким образом, все инструменты могут быть размещены либо на панелях инструментов, либо на панели вкладок. Остальная часть интерфейса конструктора отчетов очень похожа на интегрированную среду разработки Delphi.
Резюме Конечно же, компоненты rvDataSetConnection и rvProject — это далеко не все компоненты программы Rave Reports. Существует также ряд компонентов для преобразования отчетов в форматы PDF, HTML, RTF и TXT, однако их изучение выходит за рамки данной книги. При желании можно изучить их самостоятельно с помощью интерактивной справочной системы Delphi и других источников.
Что дальше? В следующей, завершающей части книги будет рассмотрена разработка приложений для Web.
-
, /
, (
-ч
.•
•
- . . • .
. .
ЧАСТЬ
IV I
V
РАЗРАБОТКА ПРИЛОЖЕНИЙ для WEB Для разработки приложений, предназначенных для работы с Web, используется специальный набор средств под названием WebBroker. Эти средства поставляются вместе с версией Delphi Enterprise, а также могут быть приобретены отдельно. Набор компонентов WebBroker находится в палитре компонентов на вкладке Internet. Приложения для Web делятся на две категории: серверные приложения и клиентские приложения. Серверные Web-приложения выполняют обработку запросов, поступающих от клиентских приложений или Web-браузеров в виде унифицированных указателей ресурсов URL (Uniform Resource Locator). Содержимое ответа Web-сервера обычно представляет собой документ, созданный с использованием языка гипертекстовой разметки HTML {HyperText Markup Language). В этой части будут кратко рассмотрены основы языка HTML, а также разработано Web-серверное и простое клиентское приложение для работы с базой данных staff .gdb. При этом автор предполагает, что читатель знаком с такими понятиями, как Web-браузер, доменное имя, IP-адрес, протоколы HTTP и FTP, и т.д.
. .
Глава 12
Основы языка HTML В большинстве случаев содержимое страниц, отображаемых Webбраузерами, представляет собой документ, созданный с использованием языка HTML. Этому языку посвящены целые книги, поэтому в данной главе будет дана только самая общая информация, позволяющая приступить к разработке приложений для Web. Но прежде, чем перейти непосредственно к изучению синтаксиса языка HTML, следует рассмотреть организацию запросов с использованием указателей URL. •
Структура указателя URL
Указатель URL состоит из названия протокола, имени сервера, имени сценария или серверного приложения с указанием полного пути и параметров запроса, передаваемого на Web-сервер. Указатели URL вводятся в Web-браузере в поле адреса или указываются в HTML-документе в дескрипторе HREF (рассматривается в следующем разделе). Рассмотрим пример URL: http://localhost/scripts/ServerApp.dll/query?TaxCode^l23456789
В данном примере используется протокол HTTP (Hypertext Transfer Protocol) — вероятно, самый популярный Internet-протокол. В URL могут использоваться и другие протоколы (например, FTP -— File Transfer Protocol — протокол передачи файлов). • В качестве имени сервера в рассмотренном примере используется localhost, что соответствует клиентскому компьютеру. • В качестве имени сервера можно указать его IP-адрес. В том случае, если Web-сервер установлен на локальном компьютере, вместо имени localhost можно указать IP-адрес 1 2 7 . 0 . 0 . 1 . Кроме того, в этой части URL можно указать имя, хранимое на сервере имен DNS. DNS — это аббревиатура термина "Domain Name Server" — сервер доменных имен. Кратко, сервер DNS — это служебный компьютер сети, преобразующий имена компьютеров, указанные в доменных записях (например, www.amazon.com), в IP-адреса. • Третья часть URL — это название каталога, содержащего серверные приложения. В случае использования сервера IIS (Internet Information Server) в среде Windows 2000 сценарии по умолчанию размещаются в каталоге c:\inetpub\scripts. Название каталога с Web-серверными приложениями и сценариями можно в URL и не указывать.
Основы языка HTML
301
• Следующей части URL соответствует название Web-серверного приложения. В данном примере запрос передается серверному приложению ServerApp.dll, представляющему собой динамически подключаемую библиотеку DLL. Без сомнения, Web-серверное приложение может быть и обычным выполняемым файлом с расширением *.ехе. — подобные приложения обычно используют протокол CGI (Common Gateway Interface — так называемый, общий шлюзовой интерфейс). • На основании следующей части URL (query) Web-серверное приложение определяет назначение запроса. В терминах средств WebBroker, используемых в Delphi, эта часть URL называется операцией (actioa). Название операции может быть каким угодно — главное, чтобы оно могло быть распознано и обработано серверным приложением. Фактически, название операции определяет характер результата, возвращаемого сервером. • Последняя часть URL содержит информацию о передаваемых на сервер параметрах. В данном примере запись TaxCode=123456789 означает, что в ответ на запрос должны быть возвращены данные о сотруднике, имеющем идентификационный код 123456789. Эта часть отделяется от названия операции знаком вопроса, а параметры отделяются друг от друга символом "&". В том случае, если при обращении к Web-серверному приложению указанная операция (в данном примере ~ query) не найдена, средства WebBroker позволяют задать 'операцию по умолчанию. ПРИМЕЧАНИЕ Часто используются URL вида http: //www.amazon. com, где не указывается никакого пути или имени сценария. В этом случае выбор возвращаемой страницы осуществляется самим Web-сервером. По умолчанию Web-сервер Microsoft IIS возвращает страницу default. asp, но его можно настроить таким образом, чтобы он возвращал любую другую страницу.
Основы языка HTML Базовым понятием в разметке HTML-документа является дескриптор (tag), еще его называют тегом, — не отображаемый элемент документа, который описывает информацию, предназначенную для вывода на экран монитора. Tagмодель, используемая в HTML-документах, подобна описанию документов, которое широко применяется в системах подготовки документов для печати. Примером такой системы является хорошо известный язык разметки научных документов ТеХ. В HTML- документах дескрипторы исполняют роль инструкций для анализатора HTML-кода. Инструкции состоят из двух дескрипторов: начального и конечного. Такие инструкции называются контейнерами. Любой конечный дескриптор отличается от начального дескриптора символом "/"• Дескрипторы в HTML различаются по именам. В общем случае дескриптор можно представить следующим образом:
Таким образом, в программах, предназначенных для интерпретации HTMLдокументов, отображается только содержимое контейнера и в том виде, который определяется дескриптором, в контейнер которого это содержимое заключено. Любой HTML-документ начинается с открывающего дескриптора , и заканчивается закрывающим дескриптором . НЕМНОГО ТЕОРИИ
-
Хотя большинство современных браузеров и могут обрабатывать HTML-документы, которые не содержат дескрипторов и , а также других дескрипторов языка HTML, все же их рекомендуется применять при создании HTML-документов. Несмотря на небольшой объем этой главы, которая представляет собой краткое введение в язык HTML, мы постараемся придерживаться спецификаций для описания конструкций языка HTML, которые поддерживаются консорциумом W3C. Документы, разрабатываемые организацией W3C, в основном носят рекомендательный характер авторам HTML-документов. Подробно с принципами создания документов на языке HTML, предлагаемыми W3C, можно ознакомиться на сайте http: //www.w3.org. В любой HTML-странице контейнер дескриптора содержит пару дескрипторов , определяющую тело документа. Все, что отображается из HTML-документа в Web-браузере, обычно является содержимым элемента body. ПРМЕЧАНИЕ Обычно, чтобы не использовать словосочетание "содержимое контейнера дескриптора body", используют следующее название содержимое элемента body. Ниже представлен пример простейшей HTML-стран и цы, отображающей в Web-браузере строку текста: "Просто строка". Просто строка Документы HTML можно создавать как при помощи простейших текстовых редакторов (например, notepad.exe), так и при помощи специализированных программ, например, подобных FrontPage. 1. Откройте текстовый редактор, например, Пуск >- Программы >• Стандартные >- Блокнот. 2. Введите представленный выше HTML-код.
Основы языка HTML
303
3. Сохраните текстовый файл, например, под именем JustStr и обязательно укажите после имени расширение .htm. 4- Запустите Web-браузер, например, Пуск >- Программы >• Internet Explorer. ПРЕДУПРЕЖДЕНИЕ Для отображения примеров HTML-кода, рассматриваемых в этой книге, лучше использовать Web-браузер Microsoft Internet Explorer версии 5.5 и выше. В более ранних версиях этого браузера или в других браузерах представленные HTML-документы могут отображаться неправильно. 5. Ведите в поле Адрес путь к файлу Juststr. htm, например, c:\mysite\JustStr.htm. Если панель Адресная строка, на которой находится поле Адрес, скрыта, щелкните мышью на строке Адресная строка в меню Вид >• Панели инструментов, для ее отображения на экране. 6. Нажмите клавишу <Enter>. 7. В результате в браузере отобразится текст, находящийся в элементе body (рис. 12.1). il С \mjjtrtoUuilSlr.hbe faun
Правка
Вид
Microtoft Inlenxd S Избрании
Се»чс
', fyipac |^J CzWiysilaSJustStr htm
Просто строка
"1 ..I
| Готово
Рис. 12.1. Простейшая HTML-страница СОВЕТ Для отображения HTML-документа в браузере, используемом в Windows по умолчанию, достаточно в Проводнике дважды щелкнуть мышью на файле, в котором храница этот HTML-документ.
Атрибуты элемента BODY Как уже указывалось в предыдущем разделе, атрибуты элемента указываются внутри его начального дескриптора, и располагаются непосредственно за именем элемента. Атрибуты представляют собой пары вида Имя_атрибута=значение, разделенных пробелами. Например, элемент body может иметь следующий вид: В данном случае атрибут background (фоновое изображение страницы) содержит ссылку на файл photo.bmp. Атрибуты элемента body перечислены в табл. 12.1.
304
Глава 12. Основы языка HTML
Таблица 12.1, Атрибуты элемента body Атрибут
Назначение
bgcolor
Цвет фона мента.
доку-
Пример использования
Описание
bgcolor=
Установка фона красного цвета.
" # F F O O O O " ИЛИ bgcolor="red"
text
Цвет основного текста документа.
text="#FFFFFF" ИЛИ
Установка белого цвета для основного текста.
text="white" background
link
vlink
alink
Заполнение фона страницы изображением из указанного файла Цвет ни разу не выбранных ссылок в HTML-документе.
background= "l.brnp"
-link="#000000"
или link="black"
Цвет уже выбранных ссылок в HTMLдокументе.
vlink=
Цвет активных ссылок в HTMLдокументе.
alink=
"ttOOFFOO" ИЛИ vlink="green"
"#OQOOFF" ИЛИ
Фон страницы будет заполнен содержимым графического файла 1 .bmp. Установка черного цвета для не выбранных ссылок. Установка зеленого цвета для уже выбранных ссылок. Установка синего цвета для активных ссылок.
alink="blue"
Цвет фона задается при помоши трех 16-битовых значений, определяющих содержимое красного, зеленого и синего цветов (RGB-модель), или при помощи имени цвета. ПРИМЕЧАНИЕ Для интерпретатора HTML-кода не имеет значения, какими символами указаны имена элементов, имена атрибутов и знамения атрибутов, — строчными или прописными (заглавными), а также не имеет значения форматирование элементов и их содержимого в документе. Важен только порядок следования элементов и их содержимого в документе. Однако, если HTML-документ большой, лучше создавать его таким, чтобы этот документ можно было легко читать в текстовом редакторе для последующего его редактирования.
Заголовок HTML-страницы Для определения заголовка HTML-страницы используется элемент title. Обычно элемент t i t l e размещается перед элементом body после начального дескриптора элемента html: Just a string
Основы языка HTML
305
Просто строка Представленный HTML-документ отобразит в строке заголовка браузера текст "Just a string".
Элементы форматирования текста В табл. 12.2 перечислены основные элементы форматирования текста HTMLдокументов с описанием их атрибутов. Таблица 12.2. Элементы форматирования текста HTML-документов Дескрипторы
Атрибуты
Описание
clear
Комментарий. Полужирный шрифт. Разрыв строки. Атрибут clear определяет способ обтекания текста вокруг изображений (возможные значения — "left", "right", "all").
<Ь> Ь>
Текст в виде цитаты.
<ега>
size
align
color face
Выделенный текст. Выбор шрифта. Атрибут size определяет размер (от 1 до 7), атрибут color — цвет, а атрибут face — название шрифта. Заголовок первого, второго ... шестого уровня. Атрибут align определяет выравнивание по горизонтали (возможные значения — "center", "left", "right").
Горизонтальный разделитель. Курсив.
align
Разрыв абзаца. Возможные значения атрибута align — "center", "left", "right".
<pre>
<strike> <strong>
Форматированный текст. Текст, расположенный в элементе рге будет отображаться в браузере в том же виде, что и в самом документе. Перечеркивание. L
Используется для выделения важных фрагментов текста. Браузеры обычно отображают такой текст полужирным шрифтом.
306
Глава 12. Основы языка HTML
Окончание таблицы 12.2 Дескрипторы
Атрибуты
Описание
<sub>
Подстрочный текст.
<sup>
Надстрочный текст.
Моноширинный шрифт.
Подчеркивание.
Элементы форматирования могут быть вложенными друг в друга. 1. Создайте следующую HTML-страницу а11дп="сег^ег">Евангелие о
.
ИоаннаЬ1>
Глaвa 1<" В начале было Слово, и GJH
Зыло у Бога, и Слово было Бог. _
2. Сохраните представленный код в файле с расширением .htm. 3. Откройте этот файл в Web-браузере. Результат показан на рис. 12.2. (ЗПР""^ формгшрования 1тая» , файл
Древка
Виа
Избранное
Сервис
Сг
Евангелие от Иоанна Глава 1 1 В начале было Слово, и Слово Сыпо у Бога, и Слово было Б о г . L^ МОЙ РО|г|ПЫС'?р
Рис. 12.2. Первый стих Евангелия от Иоанна, представленный в браузере Microsoft internet Explorer
Основы языка HTML
307
Гиперссылки Гиперссылка — это элемент HTML-документа, при щелчке мышью на котором открывается другой HTML-документ или происходит переход к другой части текущего HTML-документа. Для внедрения в тело HTML-документа гиперссылок используется элемент А.
Открытие другого документа Для открытия другого HTML-документа используется элемент А следующего вида: <а href="http://www.junior.com.ua">Издательство "Юниор"а> В браузере этой строке HTML-кода соответствует подчеркнутая фраза "Издательство "Юниор", представляющая собой гиперссылку. После щелчка мышью на этой гиперссылке будет выполнен переход к начальной странице сайта Издательства "Юниор" ПО адресу http: / / w w w . j u n i o r . c o m . u a .
Переход к закладке Закладки в тексте HTML-документа определяются при помощи атрибута name элемента А, например: <а name = "#Markl">nepBan закладкаа> Атрибут name элемента д исполняет роль внутреннего указателя, позволяющего пользователям перемещаться по Web-странице. Для отображения ссылки на какой-нибудь фрагмент, расположенный внутри HTML-документа, нужно в качестве значения атрибута href элемента А указать имя определенной закладки, соответствующей этому фрагменту, например: <а href="tMarkl">IIepeMTM к первой закладкеа>
Для перехода к закладке, определенной в другом HTML-документе используется элемент А следующего вида: <а href="http://www.my.com/index.html#Markl> Первая закладка в оглавлении
Списки В HTML-страницах можно использовать нумерованные и маркированные списки, а также списки определений терминов. Для создания нумерованного списка используется элемент ol: уа1ие="1">Первый11> f value="2">BTopou
i
308
Глава 12. Основы языка HTML
Для создания маркированных списков используется элемента!: type="disk"> <1л_>Первый1з-> <]_1>Второй11>
Атрибут type может принимать следующие значения: "disk" — маркер в виде закрашенного круга; "square" — маркер в виде закрашенного квадрата; "circle" — маркер в виде окружности. Для создания списков определений используется элемент dl: <^>Земляника<с^>Маленькая ягода
АрOys
Большая ягода Обратите внимание на то, что конечные дескрипторы и
можно не использовать. .
Вставка изображений
В HTML-документы могут быть внедрены графические изображения, которые хранятся в файлах разных форматов, например, GIF, JPEG и BMP. Для этого используется одиночный дескриптор , атрибуты которого представлены в табл. 12.3. Таблица 12.3. Атрибуты дескриптора Атрибут
Возможные значения
src
URL изобра- Указывает на файл изображения, внедряемого в HTML-документ. жения Выравнивание текста относительно изображения. "top"
align
Описание
"middle" "bottom" "left"
"right" alt
Текст
border
Число
Альтернативный текст, который будет отображаться браузерами, работающими только з тексточом режиме. В остальных случаях это значение будет использоваться в качестве всплывающей подсказки при наведении мыши на изображение в браузере. Ширина рамки вокруг изображения.
Основы языка HTML
309
Окончание таблицы 12.3 Атрибут
Возможные значения
Описание
height
Число
Высота изображения.
width
Число
Ширина изображения.
hspace
Число Число
Отступ изображения от текста по горизонтали.
vspace
•
Отступ изображения от текста по вертикали.
Приведем пример изображения, представляющего собой гиперссылку (рис. 12.3): <а href="http://www.junior.com.ua">
3 Пример изображения представляющего собой гнперссылк!) ~ Microsoft loleinel Explorer Прав*a
ЁНД
Избранное '•$$
Сервис
Й1"|ЕЭ
Справка
,xJ
гв
-jl ЛомОЙ
<Ы£ Пиек
Перейти к caurij надате^ьства Т0иж|р^
Рис. 12.3, Пример изображения, представляющего собой гиперссылку на сайт издательства "Юниор"
Табличное представление данных Для табличного представления данных в HTML-документах используется элемент table (
...
), определяющий границы таблицы. Отдельная строка в таблице определяется элементом tr (
...
), а отдельная ячейка -- элементом td (). Кроме того, при помощи элемента th (
...
) можно задать строку, содержащую заголовки столбов таблицы. Заголовок таблицы определяется при помощи элемента caption (
...
). Таблицы могут иметь внутри себя сколько угодно вложенных таблиц. Атрибуты элементов table, tr и td перечислены в табл. 12.4.
310
Глава 12. Основы языка HTML
Таблица 12.4. Атрибуты элементов table, tr и td Элемент
Атрибут
Описание
bgcolor
Цвет ячеек.
border
Ширина рамки в пикселях.
cellpadding
Расстояние между содержимым ячейки и ее границами.
cellspacing
Расстояние между ячейками.
width
Ширина таблицы в пикселях (например, width="100") или в процентном соотношении к общей ширине страницы (например, width="20%").
.
.
bgcolor
Цвет ячеек.
valign
Выравнивание по вертикали.
align
Выравнивание по горизонтали. Возможные значения: "center", "justify" (по ширине), "char" (выравнивание относительно символа, указанного в атрибуте char), "right" и "left".
bgcolor
Цвет ячеек.
char
Символ, относительно которого выполняется выравнивание
colspan
Ячейка занимает несколько столбцов (например, colspan="2" — ячейка на два столбца)
rowspan
Ячейка занимает несколько строк.
valign
Выравнивание по вертикали. Возможные значения: "top", "middle" И "bottom".
В ячейках таблицы могут содержаться разные HTML-элементы, в том числе гиперссылки и изображения. Рассмотрим пример:
HoBbiM 3aseT
Pa3flenKimrn
Oi Матфея <11>0т Марка
Основы языка HTML
311
<11>0т Луки
OT Иоанна
flpyrMe книги апостола HoaHHa
l-e послание
2-e послание
3-e послание <11>0ткроаение
Результат отображения этой HTML-страницы показан на рис. 12.4.
Формы В общем случае, форма — это объект, содержащий элементы управления. HTML-страница может содержать следующие элементы формы: текстовое поле, флажок, переключатель, кнопка и список. Все элементы формы HTML-документа располагаются между дескрипторами
Как видите, каждому из этих компонентов PageProducer соответствует HTML-страница, предназначенная для определения условий выборки данных: • компоненту ppNextSearch соответствует форма, в которой указывается условие поиска сотрудника; • компоненту ppNextFull — форма, в которой выбирается порядок сортировки списка сотрудников; • компоненту ppNextDept - - форма, в которой, кроме порядка сортировки, дополнительно указывается подразделение.
Дескрипторы подстановки Приведенное в предыдущем разделе свойство ppNextDept. HTMLDoc содержит дескриптор <#DeptsList>. Подобные дескрипторы называются дескрипто-
Операция /next
323
рами подстановки. Они обозначаются символом "#" и не распознаются браузерами, а используются только при обработке события TPageProducer. OnHTMLTag. Внутри обработчика данного события вместо дескриптора подстановки, который определяется при помощи параметра TagString, подставляется некоторая строка, которая определяется при помощи параметра ReplaceText. В данном случае задан дескриптор подстановки <#DeptsList>. Вместо него в HTML-странице необходимо вставлять раскрывающийся список с названиями подразделений. Эта подстановка реализуется в обработчике события ppNextDept.OnHTMLTag. Но сначала необходимо организовать подключение к базе данных staff.gdb.
Подключение к базе данных 1. Расположите в Web-модуле компонент SQLConnection с вкладки dbExpress палитры компонентов Delphi. 2. Присвойте его свойству Name значение sqlcStaff, свойству ConnectionName — значение iBConnection, а свойству LoginPrompt — значение False. 3. В редакторе свойства Params укажите размещение базы данных, имя роли, пользователя, пароль и диалект SQL (3). ПРЕДУПРЕЖДЕНИЕ При указании размещения базы данных используйте путь для удаленного подключения, например: localhost:e: \programs\staff\base\staff .gdb. Если указать путь для локального подключения (то есть, e:\programs\staff\base\staff.gdb), то соединение с базой данной установлено не будет. 4. Расположите в Web-модуле компонент SimpleDataSet с той же вкладки dbExpress палитры компонентов. 5. Присвойте его свойству Name значение sidsDeps, а для свойства Connection укажите ссылку на компонент sqlcStaf f. 6. Свойству sidsDeps. DataSet. Comma rid Type присвойте значение ctTable, a для свойства sidsDeps. DataSet .CoramandText укажите ссылку на имя таблицы DEPS. 7. Дважды щелкните мышью на компоненте sidsDeps, и создайте объекты для всех полей при помощи команды Add All Fields раскрывшегося контекстного меню. 8. Создайте обработчик события ppNextDept.OnHTMLTag: procedure TwmStaff.pgpNextDeptHTMLTag(Sender: TObj ect; Tag: TTag; const TagString: String; TagPararns: TStrings; var ReplaceText: String); var DeptsList: string,-
324
Глава 13. Разработка Web-серверных приложений
begin DeptsList := '<select name="Dep">'; sqlcStaff.Open; with sidsDeps do begin з Open; r while not EOF do begin DeptsList := DeptsList + '