• Название:

    Д. Осипов Delphi. Профессиональное программиро..

  • Размер: 11.16 Мб
  • Формат: PDF
  • или
  • Название: Untitled

Delphi
Профессиональное
программирование

Дмитрий Осипов

Санкт-Петербург–Москва
2006

Серия «High tech»

Дмитрий Осипов

Delphi. Профессиональное программирование
Главный редактор
Зав. редакцией
Редактор
Художник
Корректор
Верстка

А. Галунов
Н. Макарова
А. Петухов
В. Гренда
О. Макарова
Н. Гриценко

Осипов Д.
Delphi. Профессиональное программирование. – СПб.: Символ-Плюс, 2006. –
1056 с., ил.
ISBN 5-93286-074-X
Книга Д. Осипова «Delphi. Профессиональное программирование» принципиально отличается от стандартных изданий на эту тему. Это и не скороспелое
«полное» руководство по очередной версии Borland® Delphi™, и не рядовой справочник, содержащий перевод файлов помощи к среде программирования. Идея
книги в другом. Автор системно и последовательно излагает концепцию Delphi,
предоставляя читателю не просто инструмент, а профессиональную методику,
позволяющую разрабатывать эффективные приложения для Windows.
Книга рассчитана на подготовленного пользователя ПК, желающего самостоятельно научиться программировать и разрабатывать приложения и базы данных в среде Delphi. Опытные программисты смогут использовать издание как
справочник. В тексте подробно описаны более 80 компонентов VCL, функции
Object Pascal и Win32 API. В первой части книги излагаются основы языка
программирования Delphi, подробно рассматриваются библиотека визуальных
компонентов и процесс разработки собственных компонентов, изучаются динамически подключаемые библиотеки, процессы, многопоточные приложения, особенности межпрограммного взаимодействия, программирование на
Win32 API, особенности построения сетевого программного обеспечения, технологии COM и OLE-automation. Вторая часть книги посвящена проектированию и созданию реляционных баз данных. Рассматриваются реляционная модель данных и язык SQL, изучаются компоненты доступа к данным и отображения данных, базирующиеся на механизмах BDE, ADO и InterBase.
ISBN 5-93286-074-X
© Дмитрий Осипов, 2006
© Издательство Символ-Плюс, 2006
Все права на данное издание защищены Законодательством РФ, включая право на полное или частичное воспроизведение в любой форме. Все товарные знаки или зарегистрированные товарные знаки, упоминаемые в настоящем издании, являются собственностью соответствующих фирм.

Издательство «Символ-Плюс». 199034, Санкт-Петербург, 16 линия, 7,
тел. (812) 324-5353, edit@symbol.ru. Лицензия ЛП N 000054 от 25.12.98.
Налоговая льгота – общероссийский классификатор продукции
ОК 005-93, том 2; 953000 – книги и брошюры.
Подписано в печать 30.03.2006. Формат 70х1001/16 . Печать офсетная.
Объем 66 печ. л. Тираж 2000 экз. Заказ N
Отпечатано с готовых диапозитивов в ГУП «Типография «Наука»
199034, Санкт-Петербург, 9 линия, 12.

Оглавление

Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

Часть I. Программирование для Windows в среде Delphi
1.

Язык программирования Pascal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Простейшая программа на Object Pascal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Основные типы данных. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Операторы и выражения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

14
15
19
35
43

2. Процедуры и функции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Процедуры . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Функции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Особенности объявления и передачи параметров . . . . . . . . . . . . . . . . . . . . . . . . . . .
Перегрузка методов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Структура программного модуля стандартного проекта Delphi. . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Приложение 1: файлы проекта Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Приложение 2: русификация консольных приложений . . . . . . . . . . . . . . . . . . . . .

44
45
46
49
49
51
51
52

3. Базовые функции Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Математические функции и процедуры. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Функции проверки вхождения значения в диапазон . . . . . . . . . . . . . . . . . . . . . . . .
Тригонометрические функции и процедуры. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Финансовые функции и процедуры. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Статистические функции и процедуры . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Процедуры и функции для работы со строками типа AnsiString . . . . . . . . . . . . .
Процедуры и функции для работы со строками типа PChar . . . . . . . . . . . . . . . . . .
Работа с памятью . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Процедуры управления ходом выполнения программы . . . . . . . . . . . . . . . . . . . . .
Разные функции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

55
56
57
58
59
61
65
68
69
70
71

4. Основы работы с файлами . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Классификация типов файлов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Низкоуровневые методы работы с файлами . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Управление файлами, дисками и каталогами . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101

4

Оглавление

5. Введение в объектно-ориентированное программирование . . . . . . . . . . 102
Объект и класс. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Инкапсуляция. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Наследование. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Полиморфизм . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Программирование, управляемое событиями . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

103
107
109
111
112
112

6. Невидимые классы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
Основа основ – класс TObject. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Класс TPersistent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Поток – TStream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Основа компонента – класс TComponent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Элемент управления – класс TControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Оконный элемент управления – класс TWinControl. . . . . . . . . . . . . . . . . . . . . . . .
Обработка событий в классах TControl и TWinControl. . . . . . . . . . . . . . . . . . . . . .
Основа графических элементов управления – класс TGraphicControl . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

7.

115
119
120
121
125
132
136
147
148

Списки и коллекции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Набор строк – TStrings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Список – TList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Список строк – TStringList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Список объектов – класс TObjectList. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Список компонентов – класс TComponentList . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Коллекция – класс TCollection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

150
152
154
155
157
157
161

8. Стандартные компоненты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
Компоненты для редактирования текста . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Кнопки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Элементы управления – списки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Сетки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Меню . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

162
172
179
187
195
207

9. Форма, интерфейсы SDI и MDI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
Форма – TForm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Интерфейсы SDI и MDI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Приложение – класс TApplication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Особенности обработки событий в приложении
и компонент TApplicationEvents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Экран – класс TScreen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

208
222
227
235
235
238

10. Графическая подсистема . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
Представление цвета в Windows. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Перо – класс TPen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Кисть – класс TBrush. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Шрифт – класс TFont. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

240
241
243
244

Оглавление
Холст – класс TCanvas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Класс TGraphic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Пиктограмма – класс TIcon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Растровое изображение – класс TBitmap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Метафайл – класс TMetafile. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Класс TJPEGImage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Универсальное хранилище изображений – класс TPicture. . . . . . . . . . . . . . . . . .
Графические компоненты VCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Работа с графикой методами Win32 API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

5
246
254
257
258
261
263
265
266
270
280

11. Компоненты Win32 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281
Список закладок – TTabControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Блокнот – компонент TPageControl. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Иерархическая структура – TTreeView . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Графический список – TListView . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Панель инструментов – TToolBar. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Панель состояния – TStatusBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Линейка – TCoolBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Полоса управления – TControlBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Шкала – TTrackBar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

281
285
287
302
313
318
321
323
326
327

12. Для тех, кто ценит секунды . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328
Представление даты и времени в Delphi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Процедуры и функции для работы с датой и временем . . . . . . . . . . . . . . . . . . . . .
Функции конвертирования даты и времени в другие типы данных . . . . . . . . . .
Форматирование даты и времени . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Операционная система и таймер. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Таймер – компонент TTimer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Компоненты-календари – базовый класс TCommonCalendar . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

328
329
331
332
334
336
337
341

13. Работа с файлами инициализации и реестром Windows . . . . . . . . . . . . . . . 342
Файл инициализации – класс TIniFile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Реестр Windows. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Низкоуровневый доступ к реестру – класс TRegistry . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

342
346
349
355

14. Диалог с Microsoft® Windows® . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356
Диалоговые окна сообщений . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Диалог выбора каталога . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Диалоги доступа к базе данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Стандартные диалоговые окна Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

356
362
363
363
381

15. Обработка исключительных ситуаций . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382
Защищенные от ошибок секции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
Исключительные ситуации библиотеки VCL – класс Exception . . . . . . . . . . . . . 386
Принудительный вызов ИС – команда Raise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393

6

Оглавление
Расширенные возможности конструкции try .. except. . . . . . . . . . . . . . . . . . . . . .
Обработка ИС в рамках события OnException приложения TApplication . . . . .
Настройка поведения Delphi при обработке ИС . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

394
395
397
398

16. Создание компонентов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399
Выбор предка . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Эксперт компонентов. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Шаблон кода компонента . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Создание свойств . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Создание методов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Создание событий . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Пиктограмма компонента . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Подключение файла справки к компоненту . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

400
401
401
402
414
423
427
428
429

17. Централизованное управление приложением . . . . . . . . . . . . . . . . . . . . . . . . . 430
Команда – класс TAction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Компоненты-контейнеры для командных объектов . . . . . . . . . . . . . . . . . . . . . . . .
Список команд – класс TActionList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Менеджер команд – класс TActionManager. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Менеджер команд и компоненты пользовательского интерфейса. . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

431
436
438
439
446
454

18. Построение диаграмм . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455
Компонент TChart . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 477

19. Динамически подключаемые библиотеки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 478
Назначение DLL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Создание шаблона динамической библиотеки в Delphi . . . . . . . . . . . . . . . . . . . . .
Взаимодействие динамической библиотеки с проектом . . . . . . . . . . . . . . . . . . . .
Создание библиотеки ресурсов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Анализ DLL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

478
481
489
494
495
496

20. Процессы и потоки в среде Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 497
Процессы и многозадачность. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Понятие потока, многопоточность. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Элементарный поток – класс TThread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Пример простого многопоточного приложения . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Синхронизация процессов и потоков . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

497
507
508
513
517
527

21. Службы Microsoft Windows NT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 528
Администрирование служб в Windows NT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Управление службами из внешних приложений. . . . . . . . . . . . . . . . . . . . . . . . . . .
Инкапсуляция системной службы в VCL – класс TService . . . . . . . . . . . . . . . . . .
Приложение-служба – класс TServiceApplication . . . . . . . . . . . . . . . . . . . . . . . . . .
Пример проекта службы. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

528
529
538
547
548

Оглавление

7

Советы по отладке системной службы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 549
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 550

22. Обмен данными между процессами . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 551
Буфер обмена – класс TClipboard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Обмен сообщениями между процессами. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Динамический обмен данными. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Файлы, отображаемые в память. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

551
559
563
577
580

23. Обмен данными в сети . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 581
Модель взаимодействия открытых систем . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Почтовые слоты . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Место класса THandleStream в обеспечении сетевого обмена данными . . . . . . .
Введение в Network DDE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Каналы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Интерфейс сокетов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Реализация интерфейса WinSock в VCL. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Пример проекта WinSock для сети интранет . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Сокет – TRawSocket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

582
583
587
590
591
604
607
618
623
623

24. Многокомпонентная модель объектов (COM) . . . . . . . . . . . . . . . . . . . . . . . . . 624
Элементы COM-приложения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
COM-объект . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Интерфейс . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Порядок вызова сервера клиентским приложением. . . . . . . . . . . . . . . . . . . . . . . .
Реализация COM-объекта в Delphi – класс TComObject . . . . . . . . . . . . . . . . . . . . .
Пример COM-проекта . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Приложение: редактор библиотеки типов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

625
626
628
631
636
636
647
647

25. Сотрудничество с Microsoft® Office . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 650
Интерфейс IDispatch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Инициализация и деинициализация объекта автоматизации . . . . . . . . . . . . . . .
Коллекция объектов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Текстовый процессор Microsoft® Word . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Пример универсального генератора отчетов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Электронные таблицы Microsoft® Excel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Пример универсального генератора отчетов (продолжение). . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

651
652
653
655
674
676
692
693

26. Связывание и внедрение объектов – технология OLE . . . . . . . . . . . . . . . . . . 694
Место OLE-серверов в реестре Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
OLE-контейнер – компонент TOLEContainer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Пример приложения OLE-контейнера . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

694
696
702
706

27. Программирование на Win32 API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 707
Создание приложения без применения VCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 708
Получение информации о системе . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 719

8

Оглавление
Запуск программ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 723
Завершение работы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 724
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 725

28. Создание апплетов панели управления . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 726
Стандартные апплеты панели управления Windows . . . . . . . . . . . . . . . . . . . . . . .
Апплет панели управления – класс TAppletModule . . . . . . . . . . . . . . . . . . . . . . . .
Приложение панели управления – класс TAppletApplication . . . . . . . . . . . . . . .
Пример апплета панели управления . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Регистрация апплета панели управления . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

727
728
729
731
732
732

29. Пространство имен оболочки Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 733
Идентификация объекта оболочки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 734
Интерфейс папки – IShellFolder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 740
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 747

30. Мультимедиа . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 748
Проигрыватель мультимедиа – компонент TMediaPlayer . . . . . . . . . . . . . . . . . . . 748
Воспроизведение звука средствами Win32 API . . . . . . . . . . . . . . . . . . . . . . . . . . . . 757
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 758

Часть II. Разработка баз данных в среде Delphi
31. Реляционная модель данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 759
Ключевые термины реляционной базы данных . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Этапы проектирования базы данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Нормализация данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Модель данных «сущность–связь» . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Правила выбора первичного ключа . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Индексирование таблиц . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Представление (вид) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Хранимая процедура . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Триггер . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Транзакции и управление их выполнением . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

763
764
766
772
774
775
776
777
777
778
781

32. Структурированный язык запросов – SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 782
Назначение и состав языка SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Основные типы данных SQL-92 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Язык определения данных – DDL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Язык запросов – DQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Язык манипулирования данными – DML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Язык управления доступа к данным – DCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Язык обработки транзакций – TPL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Язык управления курсором – CCL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

783
784
788
795
803
805
806
807
808

33. Универсальный набор данных – класс TDataSet. . . . . . . . . . . . . . . . . . . . . . . . 809
Открытие и закрытие набора данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 810
Обновление набора данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 811

Оглавление
Перемещение по набору данных. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Создание закладок и переход к закладке . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Состояние набора данных. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Редактирование записей в наборе . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Организация доступа к отдельному полю . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Фильтрация набора данных. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Организация поиска данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Обработка событий. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Кэширование данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Взаимодействие с элементами управления данными . . . . . . . . . . . . . . . . . . . . . . .
Поддержка таблиц символов OEM и ANSI. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9
812
814
814
816
818
821
822
824
825
825
826
826

34. Работа с полями набора данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 827
Поле таблицы – класс TField . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Числовые поля – класс TNumericField . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Текстовые поля – TStringField . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Логическое поле – TBooleanField . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Бинарные поля – TBinaryField, TBytesField и TVarBytesField . . . . . . . . . . . . . .
Дата и время – поля TDateTimeField, TDateField и TTimeField . . . . . . . . . . . . . .
Дата и время – поле TSQLTimeStampField. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Поля больших двоичных объектов – TBlobField,
TGraphicField и TMemoField . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

827
850
853
855
855
855
856
856
861

35. Применение механизма BDE для доступа к данным . . . . . . . . . . . . . . . . . . . 862
Введение в Borland Database Engine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Компоненты доступа к данным BDE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Набор данных BDE – класс TBDEDataSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Соединение с объектом данных – класс TDBDataSet . . . . . . . . . . . . . . . . . . . . . . .
Таблица – TTable. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Импорт данных – TBatchMove . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Запрос – TQuery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Хранимая процедура – TStoredProc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Модифицируемый запрос – компонент TUpdateSQL . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

862
864
865
872
874
891
893
897
899
902

36. Элементы управления для работы с данными . . . . . . . . . . . . . . . . . . . . . . . . . 903
Источник данных – компонент TDataSource . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Общие черты компонентов отображения данных . . . . . . . . . . . . . . . . . . . . . . . . . .
Сетка базы данных – компонент TDBGrid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Статический текст БД – компонент TDBText . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Строка ввода БД – компонент TDBEdit. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Многострочный текстовый редактор БД – TDBMemo. . . . . . . . . . . . . . . . . . . . . . .
Редактор расширенного формата БД – TDBRichEdit . . . . . . . . . . . . . . . . . . . . . . .
Изображение БД – компонент TDBImage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Список БД – компонент TDBListBox. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Комбинированный список БД – TDBComboBox . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Флажок БД – компонент TDBCheckBox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Группа переключателей БД – компонент TDBRadioGroup . . . . . . . . . . . . . . . . . .

904
905
906
916
917
917
918
918
919
919
919
920

10

Оглавление
Компонент TDBCtrlGrid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Синхронный просмотр данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Навигатор – компонент TDBNavigator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

920
923
925
926

37. Элементы управления для работы с данными II . . . . . . . . . . . . . . . . . . . . . . . 927
Компоненты-списки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Графический список – компонент TListView . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Сетка – компонент TStringGrid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Иерархические данные . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Пример проекта иерархической БД. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

927
929
931
933
935
944

38. Место BDE в клиент-серверных приложениях . . . . . . . . . . . . . . . . . . . . . . . . . 945
Сессия – класс TSession. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Список сессий – TSessionList. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
База данных – класс TDatabase. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

946
954
955
961

39. Технология объектов данных ADO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 962
Связь между объектной моделью Microsoft ADO и библиотекой VCL . . . . . . . .
Строка соединения ADO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Соединение с источником данных ADO – компонент TADOConnection . . . . . . .
Набор данных ADO – класс TCustomADODataSet,
компонент TADODataSet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Командный объект ADO – TADOCommand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Таблица, запрос и хранимая процедура – компоненты
TADOTable, TADOQuery и TADOStoredProc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Сервисные методы модуля ADODB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

963
967
968
979
992
993
994
996

40. Компоненты InterBase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 997
Доступ к базе данных InterBase – компонент TIBDatabase . . . . . . . . . . . . . . . . . . 997
Элементарный запрос – компонент TIBSQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1005
Экспорт и импорт данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1007
Характеристики наборов данных InterBase – компонент TIBDataSet . . . . . . . 1009
Запрос – компонент TIBQuery. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1015
Хранимая процедура – компонент TIBStoredProc. . . . . . . . . . . . . . . . . . . . . . . . . 1015
Таблица – компонент TIBTable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1015
Транзакция – компонент TIBTransaction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1016
Модифицируемый запрос InterBase – компонент TIBUpdateSQL . . . . . . . . . . . 1019
Информация об объектах БД – компонент TIBExtract. . . . . . . . . . . . . . . . . . . . . 1020
События InterBase – компонент TIBEvents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1022
Информация о БД – компонент TIBDatabaseInfo. . . . . . . . . . . . . . . . . . . . . . . . . . 1023
Резюме. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1024

Заключение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1025
Литература . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1026
Алфавитный указатель . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1028

Введение

Не многие области науки могут похвастаться таким бурным развитием, какое претерпели за свою сравнительно недолгую историю существования
электронно-вычислительная техника и шагающие с ней рука об руку языки
программирования. Не так давно самые первые программы писались на языке машинных команд. Это был поистине каторжный труд. Программист тех
старозаветных времен не просто знал язык первых машин, он обладал глубокими инженерными знаниями архитектуры электронно-вычислительной
машины (ЭВМ), системы команд процессора, организации памяти и многого
другого. Такой высококлассный специалист ценился на вес золота, а производительность его работы была до смешного мала. Процесс создания элементарной программы отдаленно напоминал шаманские обряды (кто видел перфоратор, тот меня поймет), а про программистов слагались легенды.
Такое положение вещей мало кого устраивало, посему учеными предпринимались активные попытки хотя бы в какой-то степени «очеловечить» язык
машин. Первым успехом в этом направлении было создание компиляторов
с языков ассемблера. Язык низкого уровня ассемблер по-прежнему был
очень близок к машинным командам, однако в нем уже отдаленно просматривались и человеческие черты. В ассемблере машинным командам соответствовали англоязычные мнемонические коды. Синтаксис языка не отличался особой изысканностью – каждая команда ассемблера могла включать три
элемента: поле метки, код операции и поле операндов. Не так густо, но
по сравнению с машинными командами это был настоящий прорыв.
Хотя появление ассемблера и соответствующих компиляторов несколько
упростило работу программиста, но, по сути, язык мнемокодов все еще значительно отличался от языка общения людей. Однако, без всякого сомнения, можно утверждать, что ассемблер некоторым образом расширил круг
программистов и (к сожалению) снизил требования к их инженерной подготовке, скажем, с уровня шамана до уровня вождя племени. Но взамен было
получено ощутимое преимущество: разработка программы на ассемблере ускорилась если не на порядок, то, по крайней мере, в разы. Подчеркну еще
один немаловажный факт: умение творить на ассемблере не стало анахронизмом и актуально до сих пор, в особенности в области системного программного обеспечения.

12

Введение

Эра превосходства умеющих общаться с ЭВМ шаманов и вождей над обычным людом длилась совсем недолго. Конец неравенству положили новые
языки высокого (третьего) уровня. Хотя новые системы программирования
по-прежнему представляли собой компромисс между языком машин и людей, но они уже стали доброжелательными, наполнились существенным
словарным запасом, плюс ко всему семантика конструкций существенно
приблизилась к обычным человеческим фразам. Благодаря всем этим преимуществам, лишь прочитав введение в язык PL/1, ALGOL, ADA, Fortran
или во что-нибудь еще, уверенные в своих силах студенты в два счета переводили в состояние ступора ЭВМ любой степени надежности.
Девяностые годы прошлого века ознаменовались рождением языков 4-го поколения – 4GL (fourth generation languages). В них впервые вместо скучных
строк кода программист получил удивительную возможность оперировать
графическими, интуитивно понятными образами, а для создания элементарного приложения стало достаточно лишь несколько раз щелкнуть кнопкой мыши. В одно время даже раздавались восторженные возгласы о том,
что программирование стало доступным для домохозяек…
Не знаю, хорошо это или плохо, но создание профессионального программного продукта и в наши дни по-прежнему требует от человека глубоких и разносторонних знаний, терпения и внимательности, находчивости и сообразительности и, если не таланта, то, по крайней мере, творческой одаренности,
потому что программирование уже давно перестало быть просто наукой –
это уже и искусство.
В настоящей книге рассматривается один из безусловных лидеров среди современных систем программирования – среда программирования Delphi.
Это глубоко продуманный, высокоэффективный и (что немаловажно) весьма
удобный программный продукт, позволяющий создавать приложения практически любой сложности, предназначенные для работы под управлением
операционных систем Microsoft® Windows® и Linux.
Изначально Delphi специализировалась только на создании программного
обеспечения под Windows. Для этого среда снабжена глубоко проработанной
и эффективной библиотекой визуальных компонентов (VCL, Visual Components Library), элементы которой не только инкапсулировали в себе функции
прикладного программного интерфейса (API, Application Program Interface)
Windows, но и внесли существенные усовершенствования. Благодаря этому
библиотека VCL успешно конкурирует с библиотекой MFC (Microsoft Foundation Class), разработанной в корпорации Microsoft, и служит фундаментом альтернативным Microsoft Visual Studio средам программирования Borland Delphi и Borland C++.
В условиях жесткой конкуренции фирма Borland постоянно развивает и улучшает возможности среды разработки. Начиная с шестой версии Delphi в состав
среды разработки вошел пакет кроссплатформенной разработки CLX (Borland
Component Library for Cross-Platform), основанный на идеях, апробированных в VCL. Delphi 2005 впитала идеи создания распределенных программных
продуктов, базирующихся на архитектуре Microsoft® .NET Framework.

13

Введение

Особенность пакета CLX в том, что он позволяет строить приложения не только для Windows, но и для набирающей обороты ОС Linux. Тем самым программисты Delphi получили еще одно существенное преимущество – переносимость приложений между разными операционными системами. Однако за
универсальность платформы пришлось заплатить – приложения CLX вынуждены отказаться от вызова функций, специфичных для каждой из операционных систем. В связи с этим опора на CLX не столь рациональна в тех случаях,
когда вы нацелены только на работу с Windows. Поскольку настоящая книга
посвящена программированию для Windows, то мы больше не будем возвращаться к CLX и системе Kylix (дополнение к Delphi для работы с CLX).
Перечисляя заслуги Delphi, стоит упомянуть доступность и интуитивную понятность интерфейса среды, наглядность кодовых конструкций языка Object
Pascal, надежную систему выявления ошибок, высокоэффективный компилятор, умение поддерживать самые распространенные форматы баз данных
и многое другое.

Соглашения, принятые в книге
Впервые встречающиеся термины выделены полужирным шрифтом, а элементы интерфейса Delphi – шрифтом OfficinaSans.
Моноширинным шрифтом выделены имена файлов, переменных, констант, массивов, записей, методов, классов, свойств, процедур, функций, модулей
и библиотек, а также код примеров и синтаксические конструкции. Зарезервированные слова выделены моноширинным полужирным шрифтом.
Для акцентирования внимания читателя на ключевых частях материала
текст выделяется следующим образом:
На заметку.
Данный материал – советы, комментарии или замечания – следует принять к сведению.
Внимание!
Текст, отмеченный восклицательным знаком, однозначно описывает действия программиста в той или иной ситуации.
Стоп!
Однозначный запрет; внимание заостряется на характерных ошибках. Короче говоря, никогда так не делайте.
Такой картинкой отмечено описание методов из состава прикладного интерфейса пользователя Windows 32 API. Кроме того, они «переведены» с языка С
на язык Pascal.

Подробный предметный указатель позволяет найти в тексте интересующий
вас класс, компонент, свойство и метод по их названию.

1
Язык программирования Pascal
Столь популярный сегодня язык программирования Pascal своим рождением обязан профессору Цюрихской высшей технической школы Николаусу
Вирту. Язык, названный в честь французского математика Блеза Паскаля,
появился на свет в конце 1960-х годов и предназначался для обучения студентов основам структурного программирования.
Уже с самых первых дней своего существования Pascal был обречен на широчайшую популярность благодаря ряду своих неоспоримых достоинств:
гибкости и надежности, простоте и наглядности конструкций, способности
контроля правильности исходного кода на этапе компиляции, возможности
построения новых типов данных и многим другим качествам, с которыми
мы познакомимся в этой книге.
До появления Delphi 7.0 программисты говорили о «программировании на языке
Object Pascal в среде Delphi» и посмеивались над новичками, употреблявшими словосочетание «язык Delphi». Вот и досмеялись… С официальным выходом Delphi 7.0
человек, сказавший, что он создает программные продукты на «языке Delphi», окажется прав. Действительно, за десятилетие язык Object Pascal был настолько усовершенствован, что Borland официально заговорила о языке Delphi. Вместе с этим,
по крайней мере на мой взгляд, термин Object Pascal не стал атавизмом и вполне
жизнеспособен. Поэтому эта глава и называется «Язык программирования Pascal».

Достойным учеником профессора Н. Вирта стал основатель (1983 г.) фирмы
Borland International, Inc. Филипп Кан. Совместно с А. Хейльсбергом им были созданы высокоскоростные Турбо-компиляторы для языков Pascal, BASIC,
Prolog, C и Assembler.
Наилучшим примером упорного труда программистов Borland по совершенствованию языка Pascal наверное может стать приводимый ниже хронологический перечень:
• 1983 г. – год рождения пакета Turbo Pascal 1.0;
• 1985 г. – выход первой интегрированной среды Turbo Pascal 3.0;

Простейшая программа на Object Pascal
















15

1987 г. – Turbo Pascal 4.0, в состав пакета вошли графическая библиотека
и средства раздельной компиляции;
1988 г. – Turbo Pascal 5.0, дальнейшее совершенствование среды;
1989 г. – Turbo Pascal 5.5, промежуточная версия, частично включившая
в себя поддержку объектно-ориентированного программирования;
1990 г. – Turbo Pascal 6.0, многооконная среда программирования, поддержка мыши, объектно-ориентированная библиотека Turbo Vision;
1991 г. – Turbo Pascal for Windows, в состав пакета входит ключевая библиотека ObjectWindows;
1992 г. – Borland Pascal with Objects 7.0;
1995 г. – выход Borland Delphi 1.0 для работы под Microsoft Windows 3.x;
1996 г. – Borland Delphi 2.0 (первая 32-разрядная версия);
1997 г. – Borland Delphi 3.0;
1998 г. – Borland Delphi 4.0;
1999 г. – Borland Delphi 5.0;
2001 г. – Borland Delphi 6.0;
2002 г. – Borland Delphi 7.0;
2004 г. – Borland Delphi 8.0.

Простейшая программа на Object Pascal
Как-то раз я услышал неформальное, но, на мой взгляд, очень меткое определение программы: «Программа – это Идея, которую программист изложил
на языке программирования». Такое определение во главу угла ставит не сотни строк безликого кода, а Ее Величество Идею, то, без чего немыслимо существование творческой личности. Это определение – достойный ответ спорщикам на тему: «Что такое программирование – ремесло или Искусство?»
Эта глава посвящена элементарным составным частям программы на языке
Object Pascal. В целом разделы этой главы представляют фундамент, без которого изучение Delphi просто невозможно. Подчеркну, что это лишь предельно сжатый рассказ о возможностях языка Pascal.
При изложении материала, посвященного языку Pascal, мы по возможности
абстрагируемся от среды Delphi и ее фундамента – библиотеки визуальных
компонентов (VCL). На мой взгляд, первые шаги по изучению языка наиболее эффективны в консольных приложениях, где нет отвлекающих новичка
элементов управления и код максимально прост и линеен.
При запуске интегрированной среды разработки Delphi автоматически подготавливается к работе новый проект стандартного приложения для Windows. Но для консольного приложения он не подходит, поэтому закройте созданный по умолчанию проект. Для этого в главном окне Delphi выберите
пункт меню File → Close All. Затем найдите пункт File → New → Other… и щелкните по нему. Если все сделано правильно, появится окно New Items с открытой

16

Глава 1. Язык программирования Pascal

страницей New. Найдите на этой странице пиктограмму Console Application
и щелкните по кнопке OK. За этот каторжный труд Delphi отблагодарит нас заготовкой для самого простейшего приложения – консольного окна Windows.
program Project1;
{$APPTYPE CONSOLE}
uses SysUtils;
begin
{ TODO -oUser -cConsole Main : Insert code here }
end.

Прежде чем я поясню, что содержится в шаблоне кода консольного приложения, научимся сохранять плоды своей деятельности в файл. Для этого выберем пункт меню File → Save и в открывшемся диалоговом окне присвоим
своему первому проекту имя FirstPrj.dpr. В завершение нажмем кнопку ОК.
Разрабатываемая программа может включать десятки или даже сотни файлов различного типа и назначения. Но как минимум программа состоит из одного файла –
главного файла проекта Delphi. Файл такого типа идентифицируется расширением .dpr (сокращение от Delphi Project).

Теперь, когда наш проект сохранен на жестком диске компьютера, вновь обратите внимание на первую строку кода. Вместо имени проекта по умолчанию, Project1, после ключевого слова program появится предложенное нами
название FirstPrj. Следующее ключевое слово uses применяется для подключения к проекту внешних модулей, как правило, содержащих библиотеки
дополнительных подпрограмм. В частности, наш проект будет эксплуатировать наиболее часто используемую библиотеку системных утилит SysUtils.
Шаблон завершается составным оператором begin..end, внутри которого размещается выполняемый код. Пока же здесь только текст комментария.

Комментарии в тексте программы
Во всех без исключения языках программирования предусмотрена возможность комментирования строк исходного кода. В комментарии программист
в сжатом виде описывает, что делается в этих строках, для чего введена данная переменная, что произойдет после вызова процедуры. Другими словами, в комментариях разработчик кода кратко поясняет смысл рожденных
в его голове команд. В результате листинг программы становится более понятным, более читаемым и доступным для изучения.
Для того чтобы при компиляции программы текст комментариев не воспринимался Delphi как исходный код программы и не служил источником ошибок, приняты следующие соглашения. Комментарием считается:
1. Отдельная строка, начинающаяся с двух наклонных черт //:
//Одна строка комментария

2. Весь текст, заключенный в фигурные скобки {} или в круглые скобки
с символами звездочек (**):

Простейшая программа на Object Pascal

17

{Текст комментария}
(*Это также комментарий*)

Текст комментария пропускается компилятором и не оказывает влияния
на «жизнедеятельность» модуля.
Если внутри фигурных скобок на первой позиции окажется символ $, то это не что
иное, как директива компилятора. В шаблоне только что созданного нами приложения такая директива есть:
{$APPTYPE CONSOLE}

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

Компиляция и запуск программы на выполнение
Теперь научимся компилировать программу. Компилирование – это процесс, переводящий программу с языка программирования (в нашем случае
с языка Pascal) на язык машинных команд. Как-то неинтересно компилировать пустой проект, поэтому давайте научим его чему-нибудь полезному, например здороваться. В следующем листинге предложен пример такой исключительно воспитанной программы. А для того чтобы исходный код был понятнее, он буквально насквозь пропитан комментариями.
program FirstPrj;
{это листинг самой короткой и доброжелательной программы на свете}
{$APPTYPE CONSOLE}
//это директива компилятора, которую мы не трогаем
uses SysUtils; (*строка подключения внешних библиотек подпрограмм, хотя, между нами
говоря, в этой программе внешние модули нам не нужны*)
begin
WriteLn('Hello, World!'); //выводим текст «Привет, Мир!»
ReadLn;
//ожидаем ввод – нажатие любой клавиши завершит работу
end.

Повторив код программы, выберите пункт главного меню Delphi Run → Run.
Если все повторено безошибочно, то за считанные доли секунды на экране
появятся плоды нашего коллективного творчества – консольное окно со строкой «Hello, World!» Если же вдруг была допущена ошибка, то компилятор
просигнализирует о ней, выделив в листинге строку, содержащую предполагаемую ошибку или следующую за ней строку.
Вместо утомительных поисков необходимого элемента в меню у программистов Delphi наибольшей популярностью пользуется быстрая клавиша запуска программы –
функциональная клавиша F9. При нажатии этой клавиши осуществляется проверка синтаксиса проекта, его компиляция и запуск исполняемого exe-файла.

Как видите, вся программная логика сосредоточена внутри составного оператора begin..end и выполняется линейно в соответствии с очередностью следования строк.

18

Глава 1. Язык программирования Pascal

Переменные и константы
Переменная – это хранилище для данных. В самом названии заключен
смысл ее применения – переменная предназначена для работы с изменяющимися значениями. В языке Pascal могут быть объявлены переменные различных типов (об основных типах данных мы поговорим чуть позже). Для
объявления переменной используется зарезервированное слово var (сокр. от
variable). Синтаксис выглядит следующим образом:
var имя_переменной : тип_данных;

Например:
var x : Integer;
y, z : real;
s : char;
MyVar : Boolean;

//переменная x специализируется на работе с целыми числами
//переменные y и z могут хранить действительные числа
//s – символ
//MyVar – логическая переменная

Как и переменная, константа также является хранилищем для данных, но,
в отличие от переменной, константа задается раз и навсегда и не допускает
редактирования своего содержимого. Для задания константы применяется
зарезервированное слово const (сокр. от constant). Синтаксис определения
константы следующий:
const <имя_константы> [: тип данных] = <значение>;

В квадратных скобках может быть отмечен необязательный указатель на тип
константы. Приведем пример определения обычных констант:
const A = 100;
B = -3.1426;
C = 'Текст';

Для задания значения, которое будет содержаться в обычной константе, допускается применение математических выражений и результатов, возвращаемых функциями.
const D = 500;
E = D+6;
F = 3/Pi;

При определении типизированной константы явным образом указывается
тип хранящихся в ней данных:
const H : byte = 255;
I : Boolean = true;

В отличие от обычных, или как их еще иногда называют «истинных», констант, типизированные константы не рекомендуется инициализировать выражениями. В Delphi в качестве констант могут определяться массивы, записи, указатели; кроме того, существуют и экзотические константы, например процедурные.
Важно знать, в каком именно месте листинга допускается объявление переменных и констант. В консольных проектах объявление осуществляется перед составным оператором begin..end.

Основные типы данных

19

program Project1;
{$APPTYPE CONSOLE}
uses SysUtils;
const X = 10;
//объявление константы
var Y, Z : integer;
//объявление двух переменных
begin
Z:=X+Y;
//остальной код программы
end.

Идентификаторы
Идентификатор – это имя переменной, константы, массива, метода, модуля
и всего остального, что должно иметь имя. В Delphi длина идентификатора
не ограничена, но значащими являются только первые 255 символов. Идентификатор может содержать любые символы латинского алфавита, цифры
и символ нижнего подчеркивания. Первый символ идентификатора обязательно должен быть буквенный. Само собой в роли идентификаторов не допускается применять зарезервированные слова.
В отличие от С, язык программирования Pascal не критичен к регистру символов,
поэтому в Delphi следующие названия будут восприниматься как идентичные: MyValue, myvalue, MYVALUE, mYvALUE.

Если в рамках одного проекта существует несколько модулей с одинаковыми именами идентификатора, то для обращения к идентификатору требуется уточнить, кому он принадлежит.
Form1.Button1.Caption;
Form2.Button1.Caption;
Unit1.MyProcedure;

Основные типы данных
Одной из ключевых особенностей языка Object Pascal является жесткая типизация данных. Именно благодаря строгости в подходе к объявлению переменных, процедур и функций, Delphi может похвастаться одним из самых совершенных компиляторов. Что такое типизация? Все достаточно просто. Представьте себе педантичного джентльмена, любящего находить все свои вещи в
отведенных им местах: зубную щетку – в шкафчике над умывальником, а смокинг – в платяном шкафу, и никак не наоборот. Если вдруг произойдет обратное, то Delphi потеряет к нам всякий интерес, отправив сообщение об ошибке.
Более того, размеры каждой вещи нашего педанта (другими словами, объем
памяти, занимаемый переменной или объектом) соответствуют четко установленным правилам – не больше и не меньше. Однако тип данных не только накладывает ограничения на размер объекта, скажем переменной, но
и строго определяет перечень операций, которые можно производить с этим
объектом. И это правило весьма логично и последовательно – ведь совсем не

20

Глава 1. Язык программирования Pascal

стоит, например, в переменную, предназначенную для хранения целого числа, помещать пусть даже очень хорошую строку «Hello, Word!».
На рис. 1.1. предложен вариант классификации типов данных, применяемых в Delphi.
Целые
Символьные
Логические
Порядковые
Перечислимые

Простые
Действительные

Поддиапазоны
Одномерные

Массивы

Строковые

Записи

Типы данных
языка PASCAL

Множества
Структурные

Файлы

Указательные

Классы

Процедурные

Указатели на классы

Многомерные
Фиксированные
Вариантные

Вариантные

Рис. 1.1. Классификация основных типов данных языка Delphi

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



простой
строковый




структурный
указательный




процедурный
вариантный

Простые типы данных
Самым большим из представленных типов по праву считается простой. Он
предназначен для хранения данных в форме чисел или некоторых упорядоченных последовательностей. Этот тип логически разделяется на две ветви:
порядковые и действительные типы. К порядковым типам относятся:




целые числа
символьные типы
логические типы




перечислимые типы
поддиапазоны

Если первые три группы в языке Delphi описаны самым жестким образом
и не допускают каких-либо изменений, то два последних типа (перечисли-

21

Основные типы данных

мый и поддиапазон) могут определяться пользователем непосредственно
во время процесса разработки программы.
Отношения между элементами любого из порядковых типов складываются
вполне ординарно. Все элементы внутри одного типа могут располагаться
в виде упорядоченной последовательности (следующий больше предыдущего). Для всех простых типов (за исключением целых чисел) справедливо
утверждение, что первый (самый младший) элемент последовательности
имеет индекс 0, второй – 1 и т. д. Целые же числа допускают хранение и отрицательных значений, поэтому здесь самый младший элемент последовательности может начинаться не с нуля. К еще одной особенности порядковых типов данных стоит отнести отсутствие знаков после запятой.

Целые числа
В табл. 1.1 представлены порядковые целочисленные значения. В первую
очередь типы данных, описывающие целые числа, характеризуются пределом границ диапазона хранимых значений и возможностью описывать отрицательные величины. Чем больше предел допустимых значений, тем больший объем памяти будет занимать переменная этого типа. Как видно из таблицы, самым серьезным из предлагаемых типов является Int64, «пожирающий» целых 8 байт (64 бит) ОЗУ. Такой тип способен хранить величины,
сопоставимые с количеством звезд во Вселенной. Как правило, для решения
«земных» задач программисту достаточно диапазона значений типа Integer.
Таблица 1.1. Целые числа
Тип

Диапазон значений

Размер в байтах

Int64

–263 .. 263–1

8

Integer (Longint)

–2147483648 .. 2147483647

4

Smallint

–32768 .. 32767

2

Shortint

–128 .. 127

1

Byte

0 .. 255

1

Word

0 .. 65535

2

Cardinal (LongWord)

0 .. 4294967295

4

Символьные типы
Основное назначение символьного типа данных – организация вывода информации на экран компьютера и принтер. В Windows обеспечена поддержка трех наиболее важных наборов символов:
1. OEM – набор символов по умолчанию для MS-DOS.
2. ANSI – набор символов по умолчанию для Windows 9.x.
3. Unicode – набор символов по умолчанию для Windows NT/2000.
Фундаментом наборов символов OEM и ANSI служит код ASCII, в котором
каждый символ представлен значением от 0 до 127 (соответственно символ
занимает 7 бит памяти). Кодам от 0 до 31 и 127 стандартный 8-битный набор
ставит в соответствие управляющие символы (например, символы забоя, та-

22

Глава 1. Язык программирования Pascal

буляции, конца строки и возврата каретки); остальные символы могут быть
выведены на экран. Исторически сложилось, что оставшиеся символы были
закреплены за латинскими буквами.
Вскоре был задействован и восьмой бит кода, что позволило расширить код
ASCII до 256 символов («расширенный набор символов»). Этот набор символов был разработан производителями IBM PC и получил название OEM. Здесь
коды от 32 до 126 унаследованы от ASCII, а оставшиеся коды включают дополнительные символы, в частности символы псевдографики для программ DOS.
В большинстве случаев Windows и приложения под Win32 используют «набор
символов ANSI». Коды данного набора от 32 (0х20) до 127 (0х7F) соответствуют коду ASCII. Сравнительно недавно появилась еще одна кодировка, получившая название UNICODE. Один символ в такой кодировке занимает целых
два байта, и благодаря этому он может принимать одно из 65535 значений.
Итак, для работы с отдельными символами Delphi предоставляет следующие типы данных:
Таблица 1.2. Символьные типы
Тип

Кодировка

Размер в байтах

Char (AnsiChar)

ANSI

1

WideChar

UNICODE

2

Логические (булевы) типы
Логический тип применяется для хранения логических данных, способных
принимать только два значения: 1 (true/истина) и 0 (false/ложь).
Таблица 1.3. Логические типы
Тип

Диапазон значений

Размер в байтах

Boolean

0 – false; 1 – true;

1

ByteBool

от 0 до 255, где 0 – false, 1..255 – true

1

WordBool

от 0 до 65535, где 0 – false, 1..65535 – true

2

LongBool

от 0 до 4294967295, где 0 – false, 1..4294967295 – true

4

Перечислимые типы
Перечислимые типы относятся к типу данных, определяемых программистом. Перечислимый тип данных задается списком имен.
type TypeName = (Value1, Value2,..., Value19);

Числа, а также логические и символьные константы не могут быть элементами перечислимого типа. В качестве примера представим перечислимый
тип, соответствующий дням недели:
type TypeWeekDay =(Mon, Tu, We, Th, Fr, Sa, Su);
. . .
var WDay1, WDay2 : TypeWeekDay;
begin

23

Основные типы данных
WDay1 : = Mon;
WDay2 : = Tu;
end;

Особенность перечислимого типа в том, что каждому его элементу соответствует порядковый номер, начиная с 0. Наличие порядкового номера позволяет проводить операции сравнения:
if WDay1<WDay2 then …

Совместно с данными перечислимого типа зачастую используют следующие
функции:
function Pred(X);
function Succ(X);

// возвращает предшествующее значение аргумента
// возвращает следующее значение аргумента

Поддиапазоны
Переменная, входящая в поддиапазон, может принимать значения только
в пределах границ диапазона.
type SubIntegerRange = 10 .. 100;
type SubCharRange = ‘A’ .. ‘Z’;
. . .
var IntValue : SubIntegerRange;
CharValue : SubCharRange;
. . .
MyValue : = 50;
CharValue : = 'X';

При попытке присвоить переменной IntValue значение вне диапазона SubIntegerRange компилятор Delphi откажется иметь с нами дело.

Операции с порядковыми типами
Отношения порядка определяют для переменных простого типа перечень
простейших допустимых операций.
Таблица 1.4. Допустимые операции
Операция

Содержание
Порядковые функции

function Ord(X): Longint;

Возврат порядкового значения X

function Odd(X: Longint): Boolean;

Если X – нечетное число, то true, иначе false

function Succ(X);

Следующее по порядку значение X

function Pred(X);

Предыдущее значение X

procedure Inc(var X [ ; N: Longint ] ); Приращение X на N, аналог X:=X + N
procedure Dec(var X[ ; N: Longint]);

Уменьшение X на N, аналог X:=X – N

function Low(X);

Минимальное порядковое значение X

function High(X);

Максимальное порядковое значение X

function Chr(X: Byte): Char;

Возвращает символ таблицы ASCII, соответствующий порядковому значению Х

24

Глава 1. Язык программирования Pascal

Для всех порядковых типов допустима операция задания (приведения)
типа. Ее смысл – в приведении преобразования переменной к определенному типу. Для этого саму переменную заключают в круглые скобки, а перед
ними ставят название типа, к которому мы хотим привести переменную:
var

C: Cardinal;
I: Integer;
B: Byte;

begin

B:=Byte(C);
B:=Integer(I);
end;

Поясню, что происходит при задании типа. Допустим, что мы приводим переменную типа Integer к типу данных Byte. Если реальное значение, содержащееся в этой переменной, не выходит за границы, допустимые в типе данных
Byte (0...255), то значение изменениям не подвергается. Но если значение
превысит 255 или станет меньше 0, то операция задания типа включит ограничения и не допустит выхода значения за пределы диапазона Byte.
B:=Byte(-1);
B:=Byte(-2);
B:=Byte(-255);
B:=Byte(-256);

{результат
{результат
{результат
{результат

B=255}
B=254}
B=1}
B=0}

B:=Byte(511);
B:=Byte(510);
B:=Byte(257);
B:=Byte(256);

{результат
{результат
{результат
{результат

B=255}
B=254}
B=1}
B=0}

Читатель, имеющий некоторый опыт программирования, наверное, уже выявил закономерность преобразования числа при операции задания типа. Например, для типа Byte действие
B:=Byte(I);

является аналогом операции
B:=I mod 256; //результат = остаток от деления I на 256

Действительные типы
Действительные (вещественные) типы данных предназначены для работы
со значениями, содержащими не только целую, но и дробную часть.
Таблица 1.5. Действительные числа
Тип

Диапазон значений

Количество знаков Размер в байтах

Real48

2.9 x 10–39 .. 1.7 x 1038

11–12

6

Single

.5 x 10–45 .. 3.4 x 1038

7–8

4

Double (Real)

5.0 x 10–

324

15–16

8

Extended

3.6 x 10–

4951

19–20

10

Comp

–2 +1 .. 2 –1

19–20

8

Currency

–922337203685477.5808..
922337203685477.5807

19–20

8

63

.. 1.7 x 10

308

.. 1.1 x 10

4932

63

25

Основные типы данных

Если в программе необходимо производить вычисления действительных чисел, то по возможности объявляйте переменные как Single (если, конечно,
вас устраивает диапазон данного типа данных).
При осуществлении математических операций с переменными действительного типа будьте готовы к незначительным ошибкам округления.
var S : Single; R,F : Real;
begin
S:=1/3; R:=1/3;
F:=S-R;
//результат F = 0.000000009934107
end;

Предложенный листинг демонстрирует ситуацию, когда компьютер «ошибся»
в элементарных операциях вычитания. Даже второклассник знает, что 1/3 –
1/3 = 0, а наш кремниевый друг насчитал что-то около 0.000000009934107.
На самом деле в ошибке виноваты мы, а не «бестолковый» компьютер.
Ведь в программе мы использовали различные типы данных. Переменная S
объявлена как Single, поэтому она способна точно хранить лишь 7–8 знаков
после запятой, а переменная R объявлена как Real, т. е. способна хранить
15–16 знаков после запятой. В результате имеем S = 0.333333343267441,
R = 0.333333333333333.
При проектировании бухгалтерских приложений для обработки денежных величин
применяйте переменные типа Currency. Значение, переданное в переменную этого
типа данных, физически хранится в формате Int64, при этом Delphi полагает, что
четыре последних знака этого значения – знаки после запятой. Таким образом, действительное число 9,9999 обрабатывается как целое 99999, но при выводе на экран и проведении математических операций в тайне от нас Delphi делит его
на 10000, тем самым соблюдая статус кво. Вся эта казуистика позволяет избежать ошибок округления, что очень нравится бухгалтерам.

Строковый тип
Строковый тип данных предназначен для хранения последовательности
букв, цифр и других символов. Обычная строка представляет собой не что
иное, как массив символьных значений плюс некоторая служебная информация. В Delphi реализовано четыре основных строковых типа (табл. 1.6).
Таблица 1.6. Строковые типы
Тип

Максимальная длина

Размер в памяти

ShortString

255 символов

2 .. 256 байт

String

определяется директивой компилятора $H. Если она включена, то соответствует AnsiString, иначе ShortString

AnsiString

около 231 символов

4 байт .. 2 Гбайт

WideString

около 230 символов

4 байт .. 2 Гбайт

Исторически в Delphi 1.0 появился тип данных ShortString. В памяти компьютера она представляет собой цепочку байтов, причем в первом байте содер-

26

Глава 1. Язык программирования Pascal

жится значение длины текстовой строки, а в остальных – непосредственно
информация. Другими словами, если вас зовут «Петя» (что составляет 4 символа), то в служебном байте окажется четверка, а в оставшихся четырех байтах – соответственно символы «П», «е», «т» и «я».
Физический формат строки AnsiString значительно сложнее. Официальная
информация о том, каким образом создатели Delphi организовали хранение
данных в этом типе строки, отсутствует. (Это говорит о том, что Borland оставляет за собой право изменять внутренний формат строки такого типа.)
Однако серьезный программист обязан знать о следующих особенностях физического формата AnsiString.
Во-первых, это строка, заканчивающаяся нулем, – в самом последнем байте
этой строки окажется символ #0. Во-вторых, форматом строки предусмотрена область, хранящая данные о количестве ссылок на эту строку. Благодаря тому что строка завершается нулевым символом, она прекрасно взаимодействует с функциями Windows API, а из-за наличия счетчика ссылок
(хранящего данные о том, сколько строковых переменных ссылаются на одно и то же место в памяти) значительно упрощается операция копирования
строки из переменной в переменную. В этом случае просто копируется указатель и осуществляется приращение счетчика ссылок.
Строки WideString предназначены для работы с 16-битными символами, т. е.
здесь на каждый символ отводится два байта. Таким образом, тип данных WideString способен работать с символами из таблицы Unicode (UCS-2). Unicode –
стандарт, рожденный в недрах Apple и Xerox в 1988 г. Спустя три года для совершенствования и внедрения Unicode был создан консорциум, в состав которого вошли более десятка ключевых компаний, в том числе и Microsoft.
Поскольку на каждый символ отводится два байта, Unicode позволяет кодировать 65 536 символов, что более чем достаточно для работы с любым языком.
Поэтому разработчики Unicode решили определить местоположение символов
каждого из ключевых мировых языков (табл. 1.7) и расширить набор символов
огромным количеством технических символов. На сегодняшний день определено около 35 тысяч кодовых позиций и еще около 30 тысяч позиций свободны.
Таблица 1.7. Unicode
16-битный код Символы

16-битный код Символы

0000-007F

ASCII

0300-U36F

Общие диакритические

0080-00FF

Символы Latin 1

0400-04FF

Кириллица

0100-017F

Европейские латинские

0530-058F

Армянский

01 80-01FF

Расширенные латинские

0590-05FF

Еврейский

0250-02AF

Стандартные фонетические 0600-06FF

Арабский

02BO-02FF

Модифицированные литеры 0900-097F

Деванагари

При объявлении переменной строкового типа допускается явным образом ограничить ее длину, для чего в квадратных скобках указывают количество
символов в строке:

27

Основные типы данных
var Name : string[40];

Объявленная таким образом переменная занимает в памяти количество
байт, равное длине строки + 1 байт.

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

Массивы
Представим, что перед программистом поставлена задача разработки программного модуля для хранения фамилий студентов или что-нибудь похожее,
связанное с обработкой большого количества однотипных данных. Использование обычных переменных для хранения упорядоченных наборов данных
одного типа не является эффективным решением подобных задач. Явное неудобство состоит в объявлении десятков, сотен или даже тысяч переменных,
а сложность обращения к каждой из них тем более не нуждается в доказательствах. Выходом из такой ситуации является применение массивов (array).
Массив, как и переменную, необходимо объявить. Для этого нужно указать
размерность массива и тип хранимых данных:
var <имя_массива>: array [<нижняя граница> .. <верхняя граница>] of <тип_элементов>;

Если в программе будут применяться несколько однотипных массивов, то
предварительно стоит определить тип массива, а затем создавать массивы
на базе объявленного типа:
type TMyArray = Array [0..9] of integer; // массив из 10 элементов
var Array1, Array2 : TMyArray;

Для обращения к элементам массива с целью чтения или записи достаточно
указать индекс элемента в массиве:
NewArray[0]:=199;
I:=NewArray[9];

// 0-му элементу массива присваивается значение 199
// в переменную I записано содержимое 9-го элемента массива

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

Иногда полезно задавать массив в виде константы. В следующем примере
12 ячеек массива используются для хранения количества дней в месяцах високосного года:

28

Глава 1. Язык программирования Pascal
const
DaysInMonth: array [1..12] of byte = (31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);

Хотя Delphi допускает задание нижней границы массива любым числом (например,
массив DaysInMonth), но хорошей практикой считается начинать нумерацию с нуля. Кстати, методы Low() и High(), возвращающие границы массива, предназначены для работы только с массивами, начинающимися с нуля, иначе они вернут ошибочные значения.

Вполне реально объявлять «квадратные» и «кубические» массивы, а также
массивы более высоких размерностей. Например, объявление двумерного
массива размерностью 10 на 10 ячеек выглядит следующим образом:
var MyArray : Array[0..9,0..9] of cardinal;

или
var MyArray : Array[0..9] of Array[0..9] of cardinal;

Но теперь, для того чтобы обратиться к интересующей нас ячейке двумерного массива, потребуется указать 2 индекса:
MyArray[3,6]:=56;

или
MyArray[3][6]:=56;

Чем больше элементов включает массив, тем значительнее затраты памяти на хранение его содержимого. Например, двумерный массив типа DWord размерностью всего 100 на 100 элементов потребует 100×100× 8 байт = 80 000 байт оперативной
памяти. Но зачастую размерность массива определяется с запасом (на всякий случай), а в ходе выполнения программы последний не заполняется и на 50%. Жаль потерянные байты. В таких случаях применяйте сжатые (packed) массивы:
var MyArray : packed array [0..99,0..99] of cardinal;

Такой способ объявления гарантирует более рачительное использование ОЗУ, но несколько снижает скорость доступа к элементам массива.

У рассмотренного выше способа хранения данных есть один существенный
недостаток – объявив размерность массива (сделав его статическим), мы не
сможем выйти за его границы. А что делать, если заранее даже приблизительно неизвестно, сколько элементов может оказаться в массиве? В таких
случаях используют динамические массивы, которые отличаются от статических тем, что их границы могут изменяться во время работы приложения.
Естественно, объявление динамического массива выглядит несколько иначе:
var MyArray: array of INTEGER;

Как видите, при объявлении массива мы не определяем его размерность. Но
перед заполнением массива нам все-таки придется это сделать с помощью
метода SetLength():
SetLength(MyArray, 10);

//распределяем память для 10 элементов массива

29

Основные типы данных

Отсчет элементов динамического массива всегда начинается с нуля. При работе с многомерным динамическим массивом, например следующего вида:
var MyArray : Array of Array of Char;

все размерности массива можно задавать одновременно:
SetLength(MyArray, 10, 5); //распределили память для 2-мерного массива

или последовательно для каждого индекса. Массивы с переменной длиной
по разным индексам называют динамическими разреженными массивами.
SetLength(MyArray,3);
SetLength(MyArray[0],3);
SetLength(MyArray[1],2);
SetLength(MyArray[2],10);

//массив состоит из 3 строк
//в нулевой строке 3 элемента
//в первой строке 2 элемента
//во второй строке 10 элементов

Раньше для освобождения памяти, выделенной для динамического массива, применяли процедуру Finalize().
Finalize(MyArray);

Теперь это делать необязательно, т. к. при завершении работы с массивом Delphi
самостоятельно освободит занятые ресурсы.

При работе с однотипными динамическими массивами наиболее эффективным способом копирования данных из одного массива в другой считается
вызов функции Copy(). Метод умеет копировать как массив целиком, так
и некоторые его элементы.
var Arr1, Arr2 : array : of integer;

SetLength(Arr1,4)
for i:=0 to High(Arr1) do Arr1[i]:=i;
Arr2:=Copy(Arr1);
Arr2:=Copy(Arr1, 0, 2);

//заполнение массива данными
//полное копирование
//копирование части массива

Заметьте, что мы не задаем длину второго массива, она будет определена автоматически с вызовом метода Copy(). Кроме того, динамические массивы
понимают механизм ссылок.
var Arr1, Arr2 : array : of integer;

Arr2:=Arr1;

Обратите внимание: при простом копировании массивы хранят данные в разных областях ОЗУ, а при использовании оператора присваивания оба массива будут ссылаться на одну и ту же область памяти. И если теперь нулевому
элементу первого массива присвоить значение 10, то это же значение окажется в нулевой ячейке второго массива.
Массив можно передавать и как параметр метода, правда, с некоторыми ограничениями. Если параметр представляет собой статический массив, то
массив предварительно должен быть типизирован.
type TMyArray = Array [0..5] of Byte;

30

Глава 1. Язык программирования Pascal
procedure Proc1(Arr : TMyArray);
var i : integer;
begin
for i:=Low(Arr) to High(Arr) do Arr[i]:=0;
end;

Объявление процедуры с аргументом в виде динамического массива несколько проще:
procedure Proc1(Arr : Array of Byte);

Записи
Рассмотрим небольшую задачу. Необходимо организовать учет сотрудников
фирмы. Учету подлежат: фамилия работника, его заработная плата и стаж работы на предприятии в годах. На первый взгляд решение лежит на ладони –
берем три переменные типа String, Currency и Byte соответственно и с их помощью обрабатываем данные. Но эта задача решается элегантнее с помощью
механизма записей. Объявляем тип TPeople:
type
TPeople = Record
Surname
: String;
Money
: Currency;
Experience : Byte;
end;

Запись TPeople определяет единый тип данных, содержащий три разнотипных элемента, называемых полями записи. Доступ к полям записи осуществляется по их имени. Вот пример заполнения такой записи:
var People : TPeople;
//объявление переменной на основе типа данных TPeople
begin
People.Surname
:= 'Петров';
People.Money
:= 1500.55;
People.Experience := 10;
end;

Впрочем, для определения одной единственной переменной-записи совсем
необязательно ее типизировать. Приведем пример объявления такой переменной:
var S : record
Name : string;
Age : Integer;
Experience : byte;
end;

Стоит пойти дальше – объединить достоинства массива (хранилища множества однотипных данных) и записи, позволяющей хранить разнотипные
данные.
var
MyArray : Array[0..99] of TPeople; //объявлен массив записей
begin
MyArray[0].Family:='Иванов';

Основные типы данных

31

MyArray[0].Money:=1500.55;
MyArray[0].Experience:=10;
end;

В представленном выше листинге мы объявили массив MyArray, содержащий
100 элементов типа TPeople, и заполнили нулевой элемент массива.
Современный синтаксис позволяет создавать записи с различными вариантами полей. Будем называть такую запись записью с вариантным полем.
Синтаксис объявления выглядит следующим образом:
type <имя_типа_записи> = record
<поле_1>: <тип_данных_1>;
<поле_2>: <тип_данных_2>;
...
case <поле_N>: <порядковоый_тип_данных> of
значение_1: (вариант 1);
...
значение_M: (вариант 2);
end;

Отличительная особенность записи с вариантным полем – наличие внутри
нее оператора-селектора case. Если этот оператор вам пока незнаком, то отложите рассмотрение этой части материала и вернитесь к ней после того,
как прочтете раздел «Операторы», а для всех остальных я продолжу.
Допустим, что мы пишем программу для владельца гостиницы, с помощью
которой он собирается вести учет своих постояльцев. Владельцу нужны следующие данные: фамилия (Surname) и имя (FName) постояльца, а также даты
въезда (EntryDate) и выезда (ExitDate). Немного подумав, наш привередливый
заказчик потребовал, чтобы при заказе номера люкс (VIPRoom) наша программа учитывала домашний адрес постояльца, а для обычных посетителей достаточно запомнить только номер паспорта (Doc). В такой ситуации к концу
бессонной ночи мы бы изобрели запись THotelGuest.
type
THotelGuest = record
SurName, FName : string[30];
EntryDate, ExitDate: TDate;
case VIPRooms: Boolean of
//селектор проверяет значение поля VIPRooms
False: (Doc: string[15]);
//False – только номер паспорта
True: (City, Street : string[20]; //True – домашний адрес
HomeNum: smallint);
end;

Оператор case проверяет поле VIPRooms и в зависимости от принимаемого им
значения предоставляет для заполнения те или иные данные. Такой подход
позволяет значительно рациональнее использовать оперативную память
компьютера.

Множества
Множества представляют собой коллекцию однотипных значений. При определении множества можно указать диапазон значений:

32

Глава 1. Язык программирования Pascal
type TIntSet = set of 1..10;

В качестве элементов множества могут использоваться любые порядковые
типы данных:
type TCharSet = set of 'A'.. 'Z';

Кроме того, вы имеете право определять собственные элементы множества:
type TWeekDays = set of (Mo, Tu, We, Th, Fr, St, Su);

Нетерпеливый читатель спросит: «Так чем же отличаются множества от изученных ранее поддиапазонов?» Постараюсь объяснить на небольшом примере. Представьте себе, что вы работаете в театре, правда, пока не главным режиссером, а лишь осветителем. Поскольку театр небольшой, то в распоряжении осветителя лишь три прожектора:
type TLampsSet = set of (Lamp1, Lamp2, Lamp3);

Для освещения сцены осветитель может зажечь один, два или все три прожектора,
т. е. выбрать любую понравившуюся комбинацию. Если ассоциировать прожекторы
с ячейками памяти компьютера, то это всего-навсего три бита. Единица в ячейке свидетельствует о включении, а ноль – о выключении соответствующего прожектора
(рис. 1.2).

Lamp1

Lamp2

Lamp3

1

0

1

Рис. 1.2. Представление
множества в памяти компьютера

Если множество содержит всего три элемента, то общее количество возможных комбинаций составляет 23 = 8. Зарезервированное слово set способно определять множество размерностью до 256 элементов, т. е. 2256 =
1,1579208923731619542357098500869e+77 вариантов. На практике такое количество вариантов никогда не понадобится. В частности, разработчики Delphi рекомендуют использовать множество с количеством элементов не более 16.
А теперь рассмотрим несколько строк кода, демонстрирующих работу с множествами:
type TLampsSet = set of (Lamp1,
var LampsSet : TLampsSet;
begin
LampsSet:=[];
LampsSet:=[Lamp1];
LampsSet:=LampsSet+[Lamp3];
LampsSet:=LampsSet-[Lamp1];
LampsSet:=[Lamp1,Lamp2,Lamp3];
end.

Lamp2, Lamp3);

//0,0,0
//1,0,0
//1,0,1
//0,0,1
//1,1,1



полностью очистили множество
включили первый элемент
добавили третий элемент
отключили первый элемент
включили все три элемента

Если определено два однотипных множества, то между ними вполне допустимы операции сложения, вычитания, умножения и сравнения (<=, >=, =, <>).
В операциях сравнения множество X меньше или равно множеству Y (выражение (X <= Y) = True), если каждый элемент множества X является членом

Основные типы данных

33

множества Y. Множество X равно множеству Y (выражение (X = Y) = True), если все элементы множества X точно соответствуют элементам Y. Множество
X не равно множеству Y (выражение (X <> Y) = True), если хотя бы один элемент множества X отсутствует в множестве Y.
Для того чтобы проверить, включен ли элемент в множество, применяют
оператор in.
if (Lamp2 in LampsSet) then <операция 1> else <операция 2>;

Указатели
А теперь поговорим об указателях. Начнем с напоминания о том, что физически любая переменная представляет собой не что иное, как область памяти, содержащую какие-то данные. Когда мы объявим переменную MyValue :
integer, в памяти компьютера для хранения значения этой переменной будет
зарезервировано 4 байта. Содержимое переменной MyValue можно просмотреть непосредственно в этой области памяти. Объявив другую переменную,
мы заставим операционную систему отвести под эту переменную новые свободные ячейки памяти. При этом значения указателей на MyValue и новую
переменную будут различны.
Указатель представляет собой переменную, содержащую адрес области памяти. Повторюсь еще раз, т. к. это важно: указатель хранит не содержимое
памяти, а адрес ячеек памяти. Поэтому он сам не занимает никакого места,
кроме того, которое нужно для хранящегося в нем адреса. На практике это
может выглядеть следующим образом:
var MyValue : integer;
pMyValue : pointer;
begin
MyValue:=100;
pMyValue:=@MyValue; // указателю присвоен адрес переменной MyValue
end;

Обратите внимание, что в операции присваивания адреса указателю перед
названием переменной помещен символ @. Допустим, у нас есть переменная
See : Integer и мы хотим через указатель передать ей данные из переменной
MyValue. Тогда, дабы увидеть данные, хранящиеся в MyValue(), воспользуемся
следующими строками кода:
See:=INTEGER(pmyValue^);

Эквивалентом оператора @ служит метод Addr(X).Функция возвращает указатель
на объект X.
function Addr(X): Pointer;

Тип данных Pointer называют нетипизированным указателем, т. к. он может
указывать на переменную любого типа. Чаще применяют так называемые типизированные указатели, которые способны работать с переменными определенного типа. Объявление такого указателя выглядит следующим образом:
var pInt:^integer;

34

Глава 1. Язык программирования Pascal

Рассмотрим еще один небольшой пример:
var pInt
: ^integer;
MyValue : integer;
begin
MyValue:=100;
//целочисленной переменной присвоено значение 100
Pint :=Addr(MyValue); //указатель установлен в область памяти, где хранится MyValue
pInt^ :=123;
//в область памяти записано значение 123
end;

Результатом данного упражнения стало изменение значения переменной
MyValue без обращения к ней.
Если указатель пуст (ссылается «в никуда»), то он возвращает особое значение nil.
Пустой указатель называют неопределенным. В языке Object Pascal nil – это специальная константа, предназначенная для описания пустых (несуществующих) данных.

Тип PChar
Это типизированный указатель на строки, завершающиеся нулем (null-terminated strings). Дело в различии между форматом строк, используемых
в функциях Windows API, и строками языка Object Pascal. Строки Windows
и языка С не имеют определенного размера, и признаком их окончания служит нулевой символ (#0). При необходимости использовать встроенные
функции Windows вам придется использовать PChar.
Помимо PChar в Object Pascal объявлены еще два типа указателей, специализирующихся на работе с текстовыми строками, заканчивающимися нулем. Это указатели PAnsiChar и PWideChar. Указатель PAnsiChar предназначен для работы со строками Ansi,
а PWideChar – указатель на строку с 16-битными символами из таблицы Unicode.
var Buff : Array[0..12] of Char = 'Hello world!'#0;
P
: PChar;
begin
p:=@Buff[0];
p:='Hello world!';
end;

В этом примере продемонстрированы способы сопоставления текстовых данных с указателем PChar.

Вариантные типы
Универсальный тип данных. В основном он предназначен для работы с результатами заранее не известного типа. Но за универсальность приходится
платить: на переменную вариантного типа дополнительно отводится еще два
байта памяти (это не считая байт, необходимых для хранения обычной типизированной переменной).
var vUniverse
iInt
sStr
rR

:
:
:
:

variant;
integer;
string;
real;

Операторы и выражения

35

begin
iInt:=1; sStr:='Привет'; rR:=1.987;
vUniverse:=iInt; vUniverse:=sStr; vUniverse:=rR;
end;

Ни один из типов данных не позволит таких вольностей, как variant. В приведенном примере переменной vUniverse по очереди присваиваются значения
различного типа.
Вариантный тип переменной полезен при вызове объектов, поддерживающих технологию OLE Automation, хранения значений даты/времени и создания массивов переменной длины.

Операторы и выражения
С помощью операторов выполняются определенные действия (операции)
с данными различных типов. Наиболее часто используется оператор присваивания.

Оператор присваивания
С оператором присваивания мы уже познакомились на предыдущих страницах. Он обозначается символами :=. При выполнении оператора присваивания результат выражения, стоящего справа от данного оператора, присваивается переменной, находящейся слева от оператора присваивания. В результате операции X := 1+1 переменной X будет присвоено значение 2.
Не забывайте, что Pascal – строго типизированный язык. Тип переменной, стоящей
слева от оператора :=, должен быть совместим с типом, получаемым в результате
выполнения выражения. Delphi не допустит ошибок приведения типов. Для тренировки сообразительности самостоятельно найдите ошибку в следующих строках
кода:
var X, Y : real;
Z : integer;
begin
X:=2; Y:=3;
Z:=X+Y;
end;

Порядок операций
В целом порядок выполнения операций в языке Pascal соответствует правилам, принятым в обычной арифметике. Например, для вычисления выражения 2 + 5 × 10 необходимо вначале выполнить операцию умножения, а затем
сложения. Но в Delphi есть и свои исключения, которые касаются в основном операций, которые не используются в математике.

36

Глава 1. Язык программирования Pascal

Таблица 1.8. Приоритеты операций
Приоритет

Операции

1 (высший)

^

2

@, NOT

3

*, /, DIV. MOD, AND, SHL, SHR, AS

4

+, – ,OR

5 (низший)

=, <>, <, <=, >=, IN

Наивысшим приоритетом обладает операция ^, отвечающая за обращение
к переменной или полю через указатель. Далее следуют @ и not – операции
с одним операндом. Операции *, /, div, mod, and, shl, shr, as отвечают за умножение, деление и задание типа.

Арифметические операции
Арифметические операции необходимы для математических действий с целыми и вещественными типами данных. Помимо известных еще из курса
начальной школы операторов сложения, вычитания, умножения и деления,
Delphi обогащен операторами целочисленного деления.
Таблица 1.9. Арифметические операции
Оператор Операция

Тип
данных

Возвращаемый тип

Пример Результат

+

сложение

integer, real integer, real X:=5+5.7; 10.7

-

вычитание

integer, real integer, real X:=6-3.5; 2.5

*

умножение

integer, real integer, real X:=2*2;

4

/

деление

integer, real real

X:=3/2;

1.5

Div

целочисленное деление integer
(отбрасывается десятичная часть результата)

integer

X:=3
div 2;

1

Mod

целочисленное
деле- integer
ние (отбрасывается целая часть результата)

real

X:=3
mod 2;

0.5

Логические операции
В результате выполнения любой из логических операций мы можем ожидать только одно из двух возможных значений: Да (True) или Нет (False).
Таблица 1.10. Логические операции
Оператор Операция
Пример
Операции сравнения var x : INTEGER = 6;

Результат

=

Сравнение

X=5

FALSE

<>

Неравенство

X<>5

TRUE

37

Операторы и выражения
Пример

Результат

>

Оператор Операция
Больше чем

X>5

TRUE

<

Меньше чем

X<5

FALSE

>=

Больше или равно

X>=5

TRUE

<=

Меньше или равно

X<=5

FALSE

x:= NOT True;

FALSE

Логические операции var x : boolean;
NOT

AND

Логическое отрицание

Логическое умножение (конъюнкция,
логическое И) для двух выражений

x:= NOT False;

TRUE

x:= NOT Null;

NULL

x:= True AND True;

TRUE

x:= True AND False; FALSE
x:= True AND Null;

NULL

x:= False AND True; FALSE
x:= False AND False; FALSE
x:= False AND Null;

FALSE

x:= Null AND True;

NULL

x:= Null AND False; FALSE
OR

Логическое ИЛИ (сложение) для двух
выражений

x:= Null AND Null;

NULL

x:= True OR True;

TRUE

x:= True OR False;

FALSE

x:= True OR Null;

TRUE

x:= False OR True;

TRUE

x:= False OR False; FALSE

XOR

x:= False OR Null;

NULL

x:= Null OR True;

TRUE

x:= Null OR False;

NULL

x:= Null OR Null;

NULL

Исключающее ИЛИ для двух выражений x:= True XOR True;

FALSE

x:= True XOR False; TRUE
x:= False XOR True; TRUE
x:= False XOR False; FALSE

Побитовые операции
К побитовым операторам относят операторы для работы с отдельными битами переменной.

38

Глава 1. Язык программирования Pascal

Таблица 1.11. Побитовые операции
Оператор

Операция

Пример

SHL

Поразрядный сдвиг влево (после операто- 01101 SHL 1
ра shl указывается количество разрядов)

11010

SHR

Поразрядный сдвиг вправо

01101

11010 SHR 1

Результат

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

Оператор вызова
Оператор вызова предназначен для передачи управления внешней процедуре, функции или методу, обработке их кода c возвратом к выполнению оператора, следующего за оператором вызова.
Если вызываемая процедура (функция) требует каких-либо параметров, то
передавайте их в круглых скобках после имени процедуры (функции). Подробнее оператор вызова будет рассмотрен в разделе «Процедуры и функции».

Составной оператор begin..end
Зарезервированные слова begin и end означают соответственно начало и конец тела блока. Выражения, заключенные внутри составного оператора, могут рассматриваться Delphi как единый оператор.

Условный оператор if..then
Задачей оператора if..then (если … тогда) является выполнение действия по
условию, причем условие должно быть булевого типа: Да/Нет. Синтаксис
условного оператора следующий:
if <булево условие> then <оператор>;

Например, при выполнении условия X > 5 переменной Y присваивается значение Y+1.
if X>5 then Y := Y+1;

Условный оператор if..then допускает включение в свою конструкцию ключевого слова else (иначе):
if <булево условие> then <оператор1> else <оператор2>;

39

Операторы и выражения

Например:
if X>5 then Y := Y+1 else Y := Y-1;

Оператор-селектор case .. of
Конструкция оператора-селектора case..of следующая:
case <селектор> of
<константа1> : <оператор1>;
<константа2> : <оператор2>;
<константа3> : <оператор3>;
else <оператор>;
end;

Задачи секции else внутри селектора case аналогичны задачам else в операторе if
.. then .. else. Если селектору не соответствует ни одна из констант, будет
выполнен оператор, следующий за словом else. Если же в конструкции отсутствует ключевое слово else, будет выполнена следующая за оператором строка кода.

В роли селектора могут выступать переменная, выражение или функция, но
они обязательно должны быть порядкового типа. Недопустимы селекторы
строкового и действительного типов. Оператор case осуществляет проверку
на равенство значений селектора и констант оператора. Если значение селектора совпадает со значением константы, выполняется соответствующий
константе оператор.
case MyValue of
1 : X := 1;
2 : X := Y+3;
else X := 0;
end;

Оператор case обладает большей наглядностью, чем группа операторов
if..then. Но это не единственное из его достоинств. Еще одним преимуществом селектора case..of считается возможность группировки констант.
var Ch:Char;
. . .
case ch of
‘A’ .. ‘D’ : <оператор1>;
‘E’
: <оператор2>;
else <оператор3>;
end;

При использовании селектора case следите за тем, чтобы диапазоны значений констант не пересекались!
case value of
3
: Y:=value-3;
1..10 : Y:= value+1;
end;

//ошибка!!! значение 3 входит в диапазон 1..3

40

Глава 1. Язык программирования Pascal

Операторы обработки циклов
Циклы предназначены для многократного выполнения оператора (группы
операторов), заключенного внутри циклической конструкции. Delphi предлагает три различных типа циклов: for..do, repeat..until и while..do.

Цикл с параметром for..do
Цикл с параметром for..do применяется в тех случаях, когда заранее известно количество повторений, необходимых для выполнения каких-то действий. Синтаксическая конструкция выглядит следующим образом:
for <параметр цикла>:=<стартовое значение>
to (downto) <конечное значение> do <оператор>;

Отличие ключевого слова to от downto в том, что при использовании в цикле слова
to параметр цикла увеличивает свое значение на 1, а при использовании downto –
уменьшает на 1.

Параметром цикла может быть любая порядковая переменная. При каждом
проходе цикла она получает приращение (уменьшение) на единицу. Цикл
продолжается до тех пор, пока значение параметра не достигнет конечного
значения. Например, требуется обнулить все элементы массива:
MyArray : array[0..99] of integer;
for X:=0 to 99 do MyArray[X]:=0;

Если же шаг цикла отличен от 1 или заранее неизвестно количество повторений тела цикла, вместо цикла с параметром следует применять цикл с предусловием или цикл с постусловием.
Ни в коем случае не допускается изменять значения параметра внутри тела цикла
for..do. Ведь это не более чем счетчик количества итераций. Если же ваша интуиция и логика программы подсказывают, что необходимо найти решение, в котором
параметр цикла будет выступать не только в виде банального счетчика, но и в роли управляемого параметра, то вместо цикла for..do надо использовать цикл
while..do или repeat..until.

Цикл с предусловием while..do
Особенность цикла с предусловием while..do заключается в том, что код
внутри тела цикла будет выполняться до тех пор, пока соблюдается условие,
описанное в заголовке цикла. Конструкция цикла выглядит так:
while <условие - логическое выражение> do <оператор>;

Условие цикла задается в виде булевого выражения:
X :=0;
while X<=99 do
//выполнять, пока значение X не превышает 99
begin
MyArray[X]:=0;
X:=X+1;
end;

Операторы и выражения

41

Предложенный пример повторил задачу обнуления массива, но уже при помощи цикла while..do.
Цикл с предусловием уже свободен от недостатков своего предшественника –
цикла for..do. В нем можно определять любое приращение шага и не задумываться о количестве повторений тела цикла.
Организация цикла while..do требует от программиста большой внимательности, т. к. высока вероятность погони за своим хвостом, другими словами,
возникновения бесконечного цикла.

Цикл с постусловием repeat..until
Оператор repeat..until весьма схож со своим предшественником – циклом
while..do.
repeat <оператор> until <условие – логическое выражение>;

Основные отличия цикла repeat..until от while..do заключаются в следующем. Во-первых, оператор цикла repeat..until вне зависимости от логического выражения будет выполнен хотя бы один раз. Это объясняется местонахождением логического выражения: проверка условия происходит после
того, как выполнилось тело цикла. Во-вторых, в отличие от цикла while..do,
выход из цикла с постусловием осуществляется при истинности логического
выражения. Другими словами, цикл repeat..until будет выполняться до тех
пор, пока логическое выражение не соблюдается.
X :=0;
repeat
MyArray[X]:=0;
X:=X+1;
until X>=100;
//выполнять, пока значение X меньше 100

Вложенные циклы
Один из приемов работы с циклами – использование вложенных циклов.
В этом случае в теле одного цикла выполняется другой. Следующий пример
предлагает способ обнуления всех элементов двумерного массива A. Обращение к ячейкам массива производится в рамках двойного цикла for..do.
var A : Array[0..9, 0..9] of Integer;
x, y : byte;
begin
for x:=0 to 9 do
for y:=0 to 9 do A[x, y]:=0;
end;

Процедуры break и continue
Изучение циклов было бы логически незавершенным без рассмотрения процедур прерывания текущей итерации цикла (break) и внеочередного начала
следующей итерации (continue).

42

Глава 1. Язык программирования Pascal
procedure break;
procedure continue;

Рассмотрим такой пример:
var Accept : boolean;
Counter : Integer;
begin
Accept:=true;
Counter:=0;
while Accept=true do
begin
Counter:=Counter+1;
Accept:=<выражение>;
if Counter>10 then break;
end;
end;

Цикл будет прерван, когда значение переменной-счетчика (counter) превысит 10.
Другой пример. Необходимо подсчитать сумму четных чисел, входящих
в диапазон от 0 до 10. Для этого воспользуемся процедурой Continue()
и функцией Odd(), определяющей четное или нечетное число.
function Odd(X: Longint): Boolean; //X нечетное – результат true, иначе – false
var Counter, Sum : Integer;
begin
Sum:=0;
for Counter:=0 to 10 do
begin
if Odd(Counter)=true then Continue;
Sum:=Sum+Counter;
end;
Write(Sum);
ReadLn;
end;

Если переменная counter принимает четное значение, выполняется выражение sum := sum + counter, в противном случае метод Continue() принуждает
цикл начать новую итерацию (досрочно увеличивает счетчик counter на 1).

Оператор with..do
Очень удобный оператор «для лентяев». Он обеспечивает ускоренный доступ
к полям записей или объектов. Каким образом? Допустим, наша программа
использует запись вида:
var MyRecord
IntField
StrField
SnglFiled
BField
end;

:
:
:
:
:

record
integer;
string;
single;
boolean;

Резюме

43

Не будь в нашем распоряжении with..do, при заполнении полей записи мы
вынуждены были бы написать следующий код:
MyRecord.IntField:=1;
MyRecord.StrField:=’AAA’;
MyRecord.SnglFiled:=1.567;
MyRecord.BField:=false;

и т. д., повторяя из строки в строку имя владельца поля – MyRecord. А если таких полей несколько десятков? Никчемное и рутинное занятие. Лень – воистину двигатель прогресса. Разработчики Pascal предложили следующую
конструкцию:
with <объект> do
begin
<действие с полем 1 объекта>
. . .
<действие с полем N объекта>
end;

Теперь обращение к полям записи происходит без многократного упоминания имени самой записи. Оператор with..do значительно упрощает непосильный труд программиста.
with MyRecord do
begin
IntField:=1;
StrField:='AAA';
SnglFiled:=1.567;
BField:=false;
end;

Встретив конструкцию with..do, компилятор понимает, что далее речь идет
только о конкретном объекте (в нашем случае это запись MyRecord), и больше
не требует упоминания его имени.

Резюме
Безусловно, используемый в среде Delphi язык программирования Pascal
разительно отличается от своего далекого предка – языка, созданного
Н. Виртом на рубеже 60–70-х годов XX века. Перемены столь кардинальны,
что начиная с Delphi 7 компания Borland стала избегать термина Object Pascal и предпочла называть свой язык – Delphi. Но если вы новичок в программировании в среде Delphi, то первым делом познакомьтесь со старым добрым языком Pascal. Именно на нем и базируется современная среда Delphi.
Это строго типизированный язык, в основе которого стоят типы данных.
На нижнем уровне это простейшие числа, символы, строки, а на верхнем –
описание сложнейших классов. Листинг программы на языке Pascal интуитивно понятен, поскольку используются обычные английские слова и словосочетания. Поэтому даже для начинающих изучение кода не представляет
особых трудностей – как будто читаешь книгу.

2
Процедуры и функции
При наборе текста исходного кода программисты достаточно часто сталкиваются с необходимостью многократно выполнять одни и те же действия
на одном или нескольких этапах обработки данных. При тщательной проверке алгоритмов такого рода легко заметить фрагменты кода, одинаковые
по выполняемым действиям и различающиеся только значениями исходных данных. В этом случае повторяющаяся группа операторов оформляется
в виде самостоятельной программной единицы – подпрограммы – и записывается только один раз, а в соответствующих местах программы обеспечивается лишь обращение к ней.
В языке Pascal подпрограммы реализуются в виде процедур и функций, которые вводятся в программу с помощью своего описания.
Все процедуры и функции в программе должны иметь уникальные имена. Исключение составляют так называемые перегружаемые методы, специально отмеченные
директивой overload.

Процедуры
Процедура представляет собой набор сгруппированных вместе операторов,
используемых под одним именем. Процедура состоит из заголовка и тела
процедуры. Заголовок начинается ключевым словом procedure, затем следуют имя процедуры и при необходимости заключенный в круглые скобки
список параметров. Также при необходимости объявление процедуры может завершаться специальными директивами.
После вызова процедуры последовательно выполняются операторы, заключенные между ключевыми словами begin..end. Ключевому слову begin могут
предшествовать блоки объявления типов, констант и переменных (type,
const и var). Переменные, объявленные внутри тела процедуры, называются
локальными. Такое название связано с тем, что жизненный цикл этой пере-

Функции

45

менной начинается с вызовом процедуры и заканчивается в момент ее завершения. Локальные переменные недоступны извне процедуры.
procedure имя_процедуры (параметр1, …, параметрN); директивы;
локальные_объявления
begin
<операторы>
end;

Рассмотрим следующий пример. Процедура SquareRectangle() вычисляет площадь прямоугольника, а затем выводит на экран результат.
procedure SquareRectangle(X, Y : integer);
begin
Square:=X*Y;
WriteLn(Square);
end;

Для того чтобы вызвать эту процедуру из программы, необходимо указать
имя процедуры и передать ее параметры (соблюдая их последовательность):
SquareRectangle(100, 200);

Язык Object Pascal допускает любой уровень вложенности процедур и функций. Иными словами, процедура, описанная в главной программе, в свою очередь может включать внутренние процедуры и функции.

Функции
Функции, как и процедуры, предназначены для размещения дополнительных блоков внутри основной программы. Единственное отличие функции
от процедуры в том, что она всегда должна возвращать вызвавшей ее программе какое-то значение (результат своих действий). Синтаксис написания
функции почти аналогичен синтаксису процедуры. Исключение составляет
заголовок функции, который должен начинаться с ключевого слова function
и заканчиваться типом возвращаемого значения.
function имя_процедуры (список_параметров) : тип результата; директивы;
[локальные_объявления]
begin
<операторы>
end;

В каждой из функций Delphi автоматически создается идентификатор Result,
имеющий тот же тип, что и возвращаемое функцией значение. Этому идентификатору и присваивается возвращаемое функцией значение.

Вспомним предыдущий пример с расчетом площади прямоугольника и представим его в виде функции.
function SquareRectangle(X,Y : integer):integer;
begin

46

Глава 2. Процедуры и функции
Result:=X*Y;
end;

Для вызова функции из основной программы также необходимо указать ее
имя и при необходимости список ее параметров.
var Square:integer;
. . .
Square := SquareRectangle(100, 200);
{переменной Square присвоено значение, возвращаемое функцией SquareRectangle}

Особенности объявления и передачи параметров
В процедурах и функциях Delphi различают четыре основных типа параметров:
• Значения.
• Константы; параметр передается в виде константы и объявляется при
помощи ключевого слова const.
• Переменные; параметр объявляется при помощи ключевого слова var.
• Выходные параметры; объявляются при помощи ключевого слова out.
С использованием в роли параметров обычных значений мы уже знакомы
из предыдущего материала. Такой способ применяется наиболее часто. Однако при этом вы должны знать, что следующим шагом, позволяющим компилятору Delphi еще лучше оптимизировать код приложения, будет применение константы вместо обычного параметра-значения. Единственным условием определения параметра в виде константы должна быть 100-процентная
уверенность в том, что в теле функции этот аргумент не будет претерпевать
каких-либо изменений. Вот пример метода из модуля SysUtils:
function DateToStr(const DateTime: TDateTime): string;
begin
DateTimeToString(Result, ShortDateFormat, DateTime);
end;

Задача функции – преобразовать в текстовый вид значения даты/времени.
Обратите внимание, что в теле функции аргумент DateTime не подвергается
никаким модификациям, а это значит, что его целесообразно объявить в виде константы.
Как быть, если результат функции невозможно представить одним-единственным значением? Допустим, возникла необходимость одновременно вычислять не только площадь, но и периметр прямоугольника. Есть несколько
вариантов решения этой проблемы. Первый вариант находится на поверхности – мы объявляем две специализированные функции. Первая функция вычисляет площадь, а вторая – периметр. Этот вариант достаточно прост, и мы
его не рассматриваем.
Второй вариант более оригинален. Мы объявляем тип TMyRec. Это запись с двумя полями, предназначенными для хранения значений площади и периметра.

Особенности объявления и передачи параметров

47

uses SysUtils, Classes, …;
type
TMyRec = record
Square, Perimeter : REAL;
end;

А теперь описываем функцию, результатом которой будет данная запись
TmyRec:
function MyFunction(SideA, SideB : Real):TMyRec;
begin
Result.Square:=SideA*SideB;
Result.Perimeter:=2*(SideA+SideB);
end;

На мой взгляд, это весьма элегантное решение нашей задачки: для получения
площади и периметра достаточно однократно вызвать функцию MyFunction.
Однако благодаря высокой гибкости языка программирования Delphi, возможные варианты решения поставленной задачи на этом не исчерпываются.
Третий способ заключается во включении переменной в перечень параметров функции. Такой способ передачи параметра будем называть передачей
параметра по ссылке.
function MyFunction(SideA, SideB : Real; var Perimeter : Real) : Real;
begin
Result:=SideA*SideB;
Perimeter:=2*(SideA+SideB);
end;

Площадь прямоугольника мы по привычке возвращаем во внутренний идентификатор Result, а значение периметра присваиваем формальному параметру Perimeter. А дальше вашему вниманию предлагается несколько строк
кода, демонстрирующих способ вызова данной функции.
var Square, Perimeter : Real;
begin
Square:=MyFunction(5, 10, Perimeter);
WriteLn(Square);
WriteLn(Perimeter);
ReadLn;
end;

Как видно из примера, мы объявили две переменные вещественного типа.
В переменную Square (предназначенную для хранения площади) результат
вычислений помещается уже привычным для нас способом. А переменная,
в которую мы намереваемся поместить периметр прямоугольника, передается как формальный параметр. После выполнения функции результат вычислений выводится на экран.
Передача параметра по ссылке пригодится не только для возврата результатов выполнения метода. Параметр-переменная пригодится и для передачи
в процедуру какого-то значения. Однако в Delphi есть способ объявления параметров, специализирующихся только на возврате значений. Для их объ-

48

Глава 2. Процедуры и функции

явления применяют ключевое слово out. Наиболее часто этот тип аргументов
применяется при работе с технологиями COM и COBRA.
function Supports(const Instance: IInterface; const IID: TGUID; out Intf): Boolean;
begin
Result := (Instance <> nil) and (Instance.QueryInterface(IID, Intf) = 0);
end;

При определении нетипизированного параметра его имени обязательно должно
предшествовать одно из ключевых слов: var, const или out.

Еще одним достоинством языка Object Pascal является возможность передачи в процедуры и функции в качестве параметра массива или даже открытого массива. Посмотрите на пример объявления функции ArraySum() в программе OpenArrayDemo.
program OpenArrayDemo;
{$APPTYPE CONSOLE}
uses SysUtils;
function ArraySum(Param : Array of Integer):integer;
var I : Integer;
begin
Result:=0;
for I:=Low(Param) to High(Param) do Result:=Result+Param[i];
end;
var Sum : Integer;
begin
Sum:=ArraySum([1,2,3]);
WriteLn(Sum);
ReadLn;
end.

При острой необходимости Delphi позволяет программисту не типизировать
параметр.
procedure AssignFile(var F; FileName: string);

Предложенное объявление процедуры AssignFile позволяет в качестве аргумента передавать файл любого типа. В заголовке метода мы можем сразу задать параметру значение по умолчанию. Для этого после объявления типа
параметра ставим знак равенства и значение аргумента:
function MyFunc(X:integer, Y:integer = 10):integer;
begin
result :=X+Y;
end;

Теперь при вызове объявленной таким образом функции из основной программы программисту предоставляется возможность оставить параметр по
умолчанию прежним или передать через него новое значение:
var Z : integer;

49

Перегрузка методов
. . .
Z := MyFunc(2, 20);
. . .

//параметру Y передано значение 20

или
Z := MyFunc(2);

//параметр Y принимает значение по умолчанию

При описании функции параметры по умолчанию должны размещаться в конце списка. В противном случае Delphi откажется воспринимать функцию. Вот пример
ошибки при определении аргумента по умолчанию:
function MyFunc(X:integer =10, Y:integer):integer; //код ошибочен

Перегрузка методов
Поддержка перегружаемых процедур и функций позволяет сократить количество уникальных имен, что, несомненно, упрощает труд программиста.
Как правило, такие функции применяют при выполнении одинаковых действий над разными типами данных.
function Divide(X, Y: Real): Real; overload;
begin
Result := X/Y;
end;
function Divide(X, Y: Integer): Integer; overload;
begin
Result := X div Y;
end;

Заметьте, что в приведенном примере имена функций одинаковы. Однако
одноименные функции Divide работают с различными типами параметров.
Для того чтобы Delphi распознала процедуру или функцию как перегружаемую, используют ключевое слово overload.
Удобство при работе с перегружаемыми функциями заключается в том, что
компилятор самостоятельно выясняет, какой тип данных использует вызов,
и подставляет адрес нужной функции:
var
. .
A:=
B:=

A : integer; B : real;
.
Divide(10, 2);
// тип integer
Divide(10.54, 2.64); // тип real

Структура программного модуля стандартного
проекта Delphi
Создаваемый по умолчанию программный модуль (unit) состоит из двух разделов: интерфейса (interface) и реализации (implementation) (рис. 2.1).
Раздел интерфейса начинается ключевым словом interface и продолжается
до implementation. Он отвечает за взаимосвязь модуля с внешним миром. Все,

50

Глава 2. Процедуры и функции

unit Unitl;
interface
uses
Windows,Messages,SysUtils, Classes, Graphics,
Controls, Forms, Dialogs;

Раздел
интерфейса

type
TForm1 = class(TForm)
private
(Private declarations)
public
(Public declarations)
end;
var
Form1: TForm1;

Частные
объявления

Подключаемые
модули

Объявление
типов

Публичные
объявления
Глобальные
переменные

implementation

Область
реализации

(SR *.DFM)
end.

Рис. 2.1. Структура программного модуля Delphi

что размещено в этом разделе, будет доступно другим программам и модулям. Не размещайте здесь ничего лишнего, в этике взаимоотношений модулей Delphi это считается признаком дурного тона. Ключевое слово uses предваряет список модулей, с которыми взаимодействует данный программный
модуль. Delphi автоматически заполняет его составными элементами:
• Windows – объявление типов данных и констант, используемых Windows,
взаимодействие с ключевыми функциями Windows.
• Messages – числовые константы и типы данных, применяемые в сообщениях Windows.
• SysUtils – набор системных утилит.
• Classes – составные части компонентов Delphi.
• Graphics – графические элементы.
• Controls – фундамент для построения основных элементов управления.
• Forms – описание формы и приложения.
• Dialogs – стандартные диалоговые окна Windows.
Вы вправе дополнить программный модуль еще двумя разделами: инициализации
(initialization) и завершения (finalization). Их задачи сводятся соответственно к предстартовой подготовке модуля к работе и к завершению его работы.

Интерфейс может включать один или несколько блоков (а в некоторых случаях и ни одного) объявлений типов. При создании нового проекта по умол-

51

Резюме

чанию объявляется один единственный класс TForm1. Строка TForm1 = class
(TForm) извещает нас о том, что объект TForm1 наследует все свойства и возможности объекта TForm. Секции private и public предназначены для объявления полей данных и методов объекта TForm1. Поля и методы, записанные
в секции private, являются частными, т. е. доступными только самому объекту. Информация, внесенная в секцию public, может использоваться другими модулями и программами. За объявлением типов следует область глобальных переменных (ключевое слово var).
Раздел реализации (в противовес интерфейсу проекта) – частная собственность модуля. Он начинается ключевым словом implementation и завершается
словом end с точкой в конце. При создании нового проекта в разделе реализации размещена одна-единственная строка {$R *.DFM}. Это директива компилятора по подключению к проекту файла с ресурсом формы.

Резюме
Процедуры и функции – это программы в миниатюре. Их структура сильно
схожа со структурой полной программы на языке Delphi. Основное назначение процедур и функций – избавить программиста от необходимости многократного повторения одного и того же кода. Такой код выносится в отдельную процедуру или функцию и выполняется по мере необходимости. Кроме
того, разбиение программы на такие фрагменты упрощает восприятие программы в целом – повышает ее наглядность.

Приложение 1: файлы проекта Delphi
Создаваемые в среде программирования Delphi приложение, библиотека,
компонент или какой-либо другой проект включают в себя так называемые
исходные файлы – файлы, содержащие код проекта, хранящие ресурсы
и текущие установки. Каждый файл характеризуется своим специфичным
расширением. Перечень наиболее часто встречающихся типов файлов представлен в табл. П.1.1.
Таблица П.1.1. Файлы проекта Delphi
Тип файла Описание
*.dpr

Delphi Project. Глобальный файл проекта содержит исходный код главной программы приложения.

*.bpg

Borland Project Group. Группа проектов.

*.pas

Модуль с текстом программ на языке программирования Pascal.

*.dfm

Файл формы, содержащий описание формы проекта.

*.res

Файл ресурсов может содержать пиктограмму приложения, данные
о версии и ряд других служебных данных. Для редактирования файла
ресурса в среде реализован специальный редактор Image Editor.

*.dof

Опции проекта, установленные программистом в окне Project Options.

52

Глава 2. Процедуры и функции

Таблица П.1.1 (продолжение)
Тип файла Описание
*.dsk

Установки рабочего стола, настраиваемые в окне Environment Options.

*.dpk

Исходные файлы пакетов, содержащих разделяемый программный код.

*.bpl

Скомпилированный файл пакета.

*.dcu

Скомпилированный модуль, автоматически формируемый Delphi для
каждого модуля, создаваемого в проекте.

Приложение 2: русификация консольных
приложений
Достаточно часто приходится слышать вопрос: «Почему при попытке использования в консольных приложениях кириллицы на экране появляется нечитаемый набор символов?» Причиной такой казуистики является особенность
размещения наших национальных символов в таблицах OEM и ANSI. При
работе в редакторе кода Delphi исходный код программы набирается в ANSIкодировке, но при выводе на экран Windows использует таблицу OEM.
OEM

ANSI

0

1

3x

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

sp !

"

#

$

%

&

'

3x

sp !

"

#

$

%

&

'

4x

(

)

*

+

,

-

.

/

0

1

4x

(

)

*

+

,

-

.

/

0

1

5x

2

3

4

5

6

7

8

9

:

;

5x

2

3

4

5

6

7

8

9

:

;

6x

<

=

>

?

@

A

B

C

D

E

6x

<

=

>

?

@

A

B

C

D

E

7x

F

G

H

I

J

K

L

M

N

O

7x

F

G

H

I

J

K

L

M

N

O

8x

P

Q

R

S

T

U

V

W

X

Y

8x

P

Q

R

S

T

U

V

W

X

Y

9x

Z

[

\

]

^

_

`

a

b

c

9x

Z

[

\

]

^

_

`

a

b

c

10x d

e

f

g

h

i

j

k

l

m

10x d

e

f

g

h

i

j

k

l

m

u

v

w



Љ









11x n

o

p

q

r

s

t

12x x

y

z

{

|

}

~

13x В

Г

Д

Е

Ж

З

И

14x М

Н

О

П

Р

С

Т

u

Й
У

v

w

11x n

o

p

q

r

s

t

А

Б

12x x

y

z

{

|

}

~

К

Л

13x ‚

ѓ









Х

14x Њ
15x -

-

Ф

15x Ц

Ч

Ш

Щ

Ъ

Ы

Ь

Э

Ю

Я

16x а

б

в

г

д

е

ж

з

и

й







љ

‰

њ

џ

Ј

¤

Ґ

¦

§

Е

©

®

Ї

°

±

І

і

16x

Ў

ў

17x Є

«

¬

18x

18x ґ

µ



·

е



є

»

ј

Ѕ

19x

19x ѕ

ї

А

Б

В

Г

Д

Е

Ж

З

17x к

л

м

н

о

п

53

Приложение 2: русификация консольных приложений
OEM

ANSI

0

1

2

3

0

1

2

3

4

5

6

7

8

9

20x

20x И

Й

К

Л

М

Н

О

П

Р

С

21x

21x Т

У

Ф

Х

Ц

Ч

Ш

Щ

Ъ

Ы

22x

4

5

6

7

8

9

р

с

т

у

ф

х

22x Ь

Э

Ю

Я

а

б

в

г

д

е

23x ц

ч

ш

щ

ъ

ы

ь

э

ю

я

23x ж

з

и

й

к

л

м

н

о

п

24x Е

е

Є

Є

Ї

ї

Ў

ў

°



24x р

с

т

у

ф

х

ц

ч

ш

щ

25x ъ

ы

ь

э

ю

я

25x ·



¤

Обратите внимание на следующее. Во-первых, коды кириллицы в этих таблицах полностью не совпадают. Во-вторых, символы кириллицы в таблице
OEM хранятся не последовательно. После символа «п» (код символа 175)
следует несколько десятков символов псевдографики и только затем символ
«р» (код символа 224). Символ «Е» вообще выбился из общей последовательности и оказался на позиции 240.
Почему мы вспомнили эти две таблицы символов? А потому, что консольное
приложение предназначено для работы с таблицей OEM, но эту программу
мы создаем в среде Windows и, более того, работаем в ней под управлением
Windows – операционной системы, работающей с таблицей ANSI. Другими
словами, при вводе русского символа «А» (код 192 в ANSI) наше консольное
приложение воспримет его как символ псевдографики «L» (код 192 в OEM),
т. к. код кириллического «А» в таблице OEM равен 128. Задача ясна: нам необходимо решить проблему преобразования кодов символов.
Вот так (в соответствии со всем вышеизложенным) выглядит функция перевода ANSI-символа в кодировку OEM:
function ANSI_TO_ASC(ch:char):char;
var bt:byte;
begin
bt:=Byte(ch);
case bt of
168
: result:=CHAR(240);
184
: result:=CHAR(241);
192..239 : result:=CHAR(bt-64);
240..255 : result:=CHAR(bt-16);
else result:=ch;
end;
end;

{Е}
{е}
{символы от “А” до “п”}
{символы от “р” до “я”}

Обратите внимание: преобразованию подвергаются только коды символов
русского алфавита, остальные символы проходят сквозь функцию без каких-либо изменений.
А так выглядит функция преобразования строки символов ANSI в строку
OEM:
function ANSI_TO_ASC_String(st : string):string;

54

Глава 2. Процедуры и функции
var ch:char;
i:cardinal;
begin
Result:='';
for i:=1 to Length(st) do Result:=Result+ANSI_TO_ASC(st[i]);
end;

Для проверки работоспособности функций преобразования стоит повторить
следующий код:
const S1='АБВГДЕЕЖ...Я';
S2='абвгдееж...я';
begin
WriteLn(ANSI_TO_ASC_String(S1));
WriteLn(ANSI_TO_ASC_String(S2));
ReadLn;
end.

Наивно было бы полагать, что мы стали первопроходцами в вопросе русификации консольных приложений. В недрах операционной системы предусмотрена специальная функция, преобразующая строку из набора символов
текущей региональной установки в набор символов OEM:
function CharToOem(Source : PChar; Buf : Array of char) : Boolean;

В этом методе всего два параметра: Source – указатель на исходную строку,
заканчивающуюся нулем, и Buf – массив назначения, в который будет перенесена строка в наборе символов OEM. Если вы собираетесь использовать
функцию CharToOem() в своих проектах, то в строке uses указывайте модуль
Windows:
uses SysUtils, Windows;
const s='АБВГДЕЕЖ...Я';
var Buf : Array[0..255] of char;
Result : string;
begin
WriteLn(s);
CharToOem(s,Buf);
Result:=StrPas(Buf);
WriteLn(Result);
ReadLn;
end.

Обратный метод называется OemToChar(). Познакомиться с ним можно в справочной системе Delphi.

3
Базовые функции Delphi
Глава имеет справочный характер. В ней представлены основные процедуры
и функции Delphi для работы с арифметикой и тригонометрией, для преобразования чисел и форматирования строк.
Не все из описываемых в главе функций и процедур подключаются по умолчанию к проекту. Например, для использования некоторых арифметических, тригонометрических, статистических и финансовых функций и процедур в строке uses зачастую необходимо упомянуть специальный математический модуль Math.

Математические функции и процедуры
Таблица 3.1. Математические функции
Метод
Округление чисел

Описание

function Abs(X);

Возвращает абсолютное значение числа –
число по модулю:
R := Abs(-2.3); // 2.3

function Frac(X: Extended): Extended;

Возвращает дробную часть числа:
R := Frac(-123.456); // -0.456

function Int(X: Extended): Extended;

Возвращает целую часть числа:
R := Int(123.456); // 123.0

function Round(X: Extended): Int64;

Округляет до целого числа.

function Floor(X: Extended): Integer;

Округляет вещественное число до целого
в меньшую сторону, например:
R:=Floor(-2.8) // -3
R:=Floor(2.8) // 2

56

Глава 3. Базовые функции Delphi

Таблица 3.1 (продолжение)
Метод

Описание

function Ceil(X: Extended):Integer;

Округляет вещественное число до целого
в большую сторону, например:
R:=Ceil(-2.8); // -2
R:=Ceil(2.8); // 3

function Trunc(X: Extended): Int64;

Преобразует вещественное число в целое,
усекая дробную часть.

function Odd(X: Longint): Boolean;

Возвращает true, если X – нечетное число.

Математические функции
function Sqr(X: Extended): Extended;

Возведение в квадрат.

function IntPower(Base: Extended; Expo- Возводит число Base в степень Exponent
nent: Integer): Extended;
(целого типа).
function Power(Base, Exponent: Extended): Возводит число Base в степень Exponent
Extended;
(действительного типа).
function Ldexp(X: Extended; P: Integer): Умножает число Х на 2 в степени P.
Extended;
function Sqrt(X: Extended): Extended;

Квадратный корень.

function Exp(X: Real): Real;

Экспонента.

procedure Frexp(X: Extended; var Mantissa: Возвращает мантиссу и порядок заданExtended; var Exponent: Integer) register; ной величины.
function Ln(X: Real): Real;

Натуральный логарифм.

function LnXP1(X: Extended): Extended;

Натуральный логарифм числа (X+1).

function Log2(X: Extended): Extended;

Логарифм X по основанию 2.

function Log10(X: Extended): Extended;

Логарифм X по основанию 10.

function LogN(N, X: Extended): Extended;

Логарифм X по основанию N.

Функции проверки вхождения значения в диапазон
Таблица 3.2. Функции проверки вхождения значения в диапазон
Функция

Описание

function InRange(const AValue, AMin,
AMax: Integer): Boolean; overload;
function InRange(const AValue,
AMax: Int64): Boolean; overload;
function InRange(const AValue,
AMax: Double): Boolean; overload;

Перегружаемые версии функции, провеAMin, ряющей принадлежность значения AValue
диапазону от AMin до AMax. При выполнении
AMin, условия возвращают true.

function EnsureRange(const AValue, AMin, Перегружаемые функции, возвращающие
AMax: Integer): Integer; overload;
значение, гарантированно входящее в диа-

57

Тригонометрические функции и процедуры

Функция

Описание

function
EnsureRange(const
AValue, пазон от AMin до AMax. Если AValue принадлеAMin, AMax: Int64): Int64; overload;
жит диапазону, то функция возвратит AValue. Если AValue меньше минимально допусfunction
EnsureRange(const
AValue,
тимого значения, то функция возвратит
AMin, AMax: Double): Double; overload;
AMin. Если AValue превышает верхнюю границу диапазона, то функция возвратит AMax.
function VarInRange(const AValue, AMin, Значением true сигнализирует о вхождении
AMax: Variant): Boolean;
значения AValue в диапазон AMin..AMax. Все
параметры вариантного типа.
function Low(X);

Возвращает значение самого малого аргумента из диапазона (массива или строки).

function High(X);

Возвращает значение самого большого аргумента из диапазона (массива или строки).

Тригонометрические функции и процедуры
Таблица 3.3. Тригонометрические функции
Функция

Описание

function Pi: Extended;

Значение константы Pi = 3.14159…

function Cos(X: Extended): Extended;

Косинус в радианах.

function ArcCos(X: Extended): Extended;

Арккосинус.

function Cosh(X: Extended): Extended;

Гиперболический косинус.

function ArcCosh(X: Extended): Extended;

Гиперболический арккосинус.

function Sin(X: Extended): Extended;

Синус в радианах.

function ArcSin(X: Extended): Extended;

Арксинус. Значение Х должно быть в пределах от –1 до 1. Возвращает значение
в радианах.

function Sinh(X: Extended): Extended;

Гиперболический синус.

function ArcSinh(X: Extended): Extended;

Гиперболический арксинус.

function Tan(X: Extended): Extended;

Тангенс.

function ArcTan(X: Extended): Extended;

Арктангенс.

function ArcTanh(X: Extended): Extended;

Гиперболический арктангенс. Значение Х
должно быть в пределах от –1 до 1.

function
tended;

ArcTan2(Y,

X:

Extended):

Ex- Вычисляет ArcTan(Y/X) и возвращает
угол в правильном квадранте. Значения X
и Y должны быть между – 264 и 264. Кроме
того, значение X не может быть равно 0.

function Cotan(X: Extended): Extended;

Котангенс. X<>0.

procedure SinCos(Theta: Extended;
var Sin, Cos: Extended); register;

Вычисление синуса и косинуса.

58

Глава 3. Базовые функции Delphi

Таблица 3.3 (продолжение)
Функция

Описание

function Hypot(X, Y: Extended): Extended; Расчет гипотенузы.
Пересчет величин
function CycleToRad(Cycles:
Extended;

Extended): Циклы в радианы
radians = 2pi * cycles

function RadToCycle(Radians:
Extended;

Extended): Радианы в циклы
cycles = radians/(2pi)

function DegToRad(Degrees: Extended): Ex- Градусы в радианы
tended;
radians = degrees(pi/180)
function RadToDeg(Radians: Extended): Ex- Радианы в градусы
tended;
degrees = radians(180/pi)
function GradToRad(Grads: Extended): Ex- Грады в радианы
tended;
radians = grads(pi/200)
function RadToGrad(Radians:
Extended;

Extended): Радианы в грады
grads = radians(200/pi)

Большинство представленных в табл. 3.3 тригонометрических функций требуют подключения модуля Math. Исключение составляют функции: Pi(),
Cos(), Sin() и ArcTan().
Если вы стремитесь создать максимально компактное приложение, будьте предельно скромны при использовании дополнительных модулей. Например, для возведения
числа X в степень Y совсем не обязательно подключать к проекту модуль Math, дабы
воспользоваться его функцией Power(). Вспомните, что XY = exp(Y × Ln(X)).
Я уверен, что в большинстве случаев достаточно возможностей модуля System.

Финансовые функции и процедуры
Таблица 3.4. Финансовые функции
Функция

Описание

function DoubleDecliningBalance(Cost, Salvage: Вычисляет амортизацию актива меExtended; Life, Period: Integer): Extended;
тодом двойного баланса.
function FutureValue(Rate: Extended; NPeriods: Вычисляет будущее значение капиInteger; Payment, PresentValue: Extended; Pay- таловложения.
mentTime: TPaymentTime): Extended;
function InterestPayment(Rate: Extended; Period, Вычисляет проценты по оплате ссуNPeriods: Integer; PresentValue, FutureValue: ды.
Extended; PaymentTime: TPaymentTime): Extended;
function InterestRate(NPeriods: Integer; Pay- Вычисляет норму прибыли для поment, PresentValue, FutureValue: Extended; Pay- лучения запланированной суммы.
mentTime: TPaymentTime): Extended;

59

Статистические функции и процедуры

Функция

Описание

function InternalRateOfReturn(Guess: Extended; Вычисляет внутреннюю скорость
const CashFlows: array of Double): Extende
оборота капиталовложения для проведения последовательных выплат.
function NetPresentValue(Rate: Extended; const Вычисляет текущее значение стоиCashFlows: array of Double; PaymentTime: TPay- мости вложения для проведения поmentTime): Extended;
следовательных выплат с учетом
процентной ставки.
function NumberOfPeriods(Rate, Payment, Pre- Возвращает число периодов, за коsentValue, FutureValue: Extended; PaymentTime: торые вложение достигнет заданной
величины.
TPaymentTime): Extended;
function Payment(Rate: Extended; NPeriods: Inte- Расчет размеров периодической выger; PresentValue, FutureValue: Extended; Pay- платы для погашения ссуды.
mentTime: TPaymentTime): Extended;
function PeriodPayment(Rate: Extended; Period, Расчет платежей по процентам за
NPeriods: Integer; PresentValue, FutureValue: определенный период.
Extended; PaymentTime: TPaymentTime): Extended;
function PresentValue(Rate: Extended; NPeriods: Текущее значение вложения.
Integer; Payment, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
function SLNDepreciation(Cost, Salvage:
tended; Life: Integer): Extended;

Ex- Расчет амортизации методом постоянной нормы.

function SYDDepreciation(Cost, Salvage:
tended; Life, Period: Integer): Extended;

Ex- Расчет амортизации при помощи весовых коэффициентов.

Все финансовые функции и процедуры требуют подключения модуля Math.

Статистические функции и процедуры
Таблица 3.5. Статистическме функции
Функция
Сравнение чисел

Описание

function Max(A,B: Integer): Integer; Возвращает наибольшее из двух чисел. Функция перегружаемая и работает как с целыми,
так и с действительными типами.
function Min(A,B: Integer): Integer; Возвращает наименьшее из двух чисел. Функция перегружаемая и работает как с целыми,
так и с действительными типами.
function MaxIntValue(const Data: ar- Возвращает наибольшее число из массива цеray of Integer): Integer;
лых чисел Data.
function MinIntValue(const Data: ar- Возвращает наименьшее число из массива цеray of Integer): Integer;
лых чисел Data.
function MaxValue(const Data: array Возвращает наибольшее число из массива дейof Double): Double;
ствительных чисел Data.

60

Глава 3. Базовые функции Delphi

Таблица 3.5 (продолжение)
Функция

Описание

function MinValue(const Data: array Возвращает наименьшее число из массива дейof Double): Double;
ствительных чисел Data.
Суммы, среднее арифметическое, дисперсия
function Mean(const Data: array of Возвращает среднее арифметическое от массиDouble): Extended;
ва чисел действительного типа.
function StdDev(const Data: array of Возвращает
Double): Extended;
ние.

среднеквадратическое

отклоне-

procedure MeanAndStdDev(const Data: Одновременно вычисляет среднее арифметичеarray of Double; var Mean, StdDev: ское и среднеквадратическое отклонение.
Extended);
function Sum(const Data: array of Сумма всех элементов массива действительных
Double): Extended; register;
чисел.
function SumInt(const Data: array of Сумма всех элементов массива целых чисел.
Integer): Integer register;
function SumOfSquares(const
array of Double): Extended;

Data: Сумма квадратов всех элементов массива.

procedure SumsAndSquares(const Da- Одновременно вычисляет сумму и сумму квадta: array of Double; var Sum, SumOf- ратов элементов массива.
Squares: Extended) register;
function Norm(const Data: array of Возвращает квадратный корень из суммы
Double): Extended;
квадратов (норму).
function Variance(const Data: array Вычисляет статистическую типовую дисперof Double): Extended;
сию из массива данных.
function TotalVariance(const
array of Double): Extended;

Data: Полная дисперсия чисел.
SUM(i=1,N)[(X(i) - Mean)**2]

function PopnVariance(const
array of Double): Extended;

Data: Вычисляет дисперсию совокупности всех значений в массиве данных, используя n (смещенный) метод: TotalVariance / n.

procedure
MomentSkewKurtosis(const Вычисляет первые четыре порядка M1...M4 стаData: array of Double; var M1, M2, тистических моментов, асимметрию Skew и эксцесс Kurtosis для массива данных Data.
M3, M4, Skew, Kurtosis: Extended);
Генерация псевдослучайного числа
Автоматическая инициализация генератора
псевдослучайных чисел.

procedure Randomize;
function RandG(Mean,
tended): Extended;

StdDev:

Ex- Генерирует псевдослучайное число, используя
распределение Гаусса.

function Random [( Range: Integer) ]; Генерирует псевдослучайное число в пределах
от 0 до значения Random. Для установки начального значения генератора псевдослучайных чисел используйте переменную RandSeed: LongInt.

Процедуры и функции для работы со строками типа AnsiString

61

Все статистические функции и процедуры (за исключением Random и Randomize) требуют подключения модуля Math.

Процедуры и функции
для работы со строками типа AnsiString
Таблица 3.6. Функции для работы со строками типа AnsiString
Функция
Удаление пробелов

Описание

function
Trim(const
string): string;

S: Удаляет все пробелы и управляющие символы из текстовой строки.

function TrimLeft(const
string): string;

S: Удаляет пробелы в начале строки и все управляющие
символы.

function TrimRight(const
string): string;

S: Удаляет пробелы в конце строки и все управляющие
символы.

Сравнение строк
* function
CompareText(const Сравнивает две строки и возвращает 0, если строки
S1, S2: string): Integer;
идентичны. Функция не учитывает регистр символов.

function CompareStr(const Сравнивает две строки и возвращает 0, если строки
S1, S2: string): Integer;
идентичны. Функция чувствительна к регистру символов.

*

function
AnsiSameText(const Сравнивает две строки без учета регистра, но при этом
контролируется кодовая страница строки. Если строS1, S2: string): Boolean;
ки идентичны, возвращает true.
Форматирование строк
** function Format(const For- Функция возвращает текстовую строку, заполняя ее
mat: string; const Args: ar- аргументами из массива Args. Место вставки аргументов и их форматирование определяются в строке Forray of const): string;
mat() (см. табл. 3.7 и 3.8).
** procedure FmtStr(var StrRe- Аналогично функции Format(). Результат помещается
sult: string; const Format: в строку StrResult.
string; const Args: array of
const);

function LowerCase(const S: Возвращает строку в нижнем регистре.
string): string;

*

function UpperCase(const S: Возвращает строку в верхнем регистре.
string): string;

*

*

**

Для работы со строками с учетом языкового драйвера предусмотрены функции,
аналогичные рассмотренным в таблице. Это функции ANSI: AnsiCompareStr() –
аналог CompareStr(), AnsiLowerCase() – аналог LowerCase() и т. д.
Шаблон в строке форматирования имеет следующую форму:
"%" [index ":"] ["-"] [width] ["." prec] type

62

Глава 3. Базовые функции Delphi

Таблица 3.6 (продолжение)
Функция
function
QuotedStr(const
string): string;

Описание
S: Возвращает строку S в одинарных кавычках.

Преобразование других типов данных к string
function IntToStr(Value: Integer): Преобразует целое число в строку.
string;
function StrToInt(const S: string): Преобразует строку в целое число.
Integer;
function
StrToIntDef(const
S: Работает как StrToInt(), но при ошибке преобраstring; Default: Integer): Integer; зования возвращает значение Default.
function IntToHex(Value: Integer; Преобразует целое число в строку, соответствуюDigits: Integer): string;
щую его шестнадцатеричному представлению.
function
FloatToStr(Value:
tended): string;

Ex- Преобразует число с плавающей точкой в строку.

function FloatToStrF(Value: Ex- Преобразует значение Value в строку в соответстtended; Format: TFloatFormat; Pre- вии с форматом:
cision, Digits: Integer): string;
type TFloatFormat = (ffGeneral, ffExponent,
ffFixed, ffNumber, ffCurrency);
Точность преобразования определяется в Precision. Для типа данных Single точность обычно
равна 7, Double – 15, Extended – 18. Параметр Digits определяет число знаков после запятой.
function FormatFloat(const Format: Наиболее совершенная функция преобразования
string; Value: Extended): string; вещественного числа в текстовую строку. Форматирование осуществляется в соответствии
со спецификаторами формата в параметре Format
(см. табл. 3.9).
function
FormatMaskText(const Возвращает строку Value, отформатированную
EditMask: string; const Value: в соответствии с маской EditMask (см. формат
шаблона TMaskEdit в главе 8 «Стандартные комstring): string;
поненты»). Функция объявлена в модуле Mask.
function
CurrToStr(Value:
rency): string;

Cur- Преобразует денежный тип в строку.

function
StrToCurr(const
string): Currency;

S: Преобразует строку в денежный тип.

function CurrToStrF(Value: Cur- Преобразует денежный тип в строку в соответстrency; Format: TFloatFormat; Dig- вии с форматом
its: Integer): string;
Format : TFloatFormat.
function FormatCurr(const Format: Аналогична функции FormatCurr(). Преобразует
string; Value: Currency): string; денежный тип в строку (см. табл. 3.9).
function
QuotedStr(const
string): string;

S: Возвращает значение S внутри одинарных кавычек (').

Процедуры и функции для работы со строками типа AnsiString

Функция

63

Описание

function AnsiQuotedStr(const
string; Quote: Char): string;

S: Возвращает значение S внутри символа, определенного параметром Quote.

Другие функции
procedure Delete(var S: string; In- Удаляет из строки S Count символов, начиная
dex, Count:Integer);
с позиции Index.
s:='Borland Delphi';
Delete(s,1,7); // Delphi
function Concat(s1[, s2,..., sn]: Возвращает объединение двух и более строк.
string): string;
function Copy(S; Index, Count: In- Возвращает субстроку из Count символов, начиteger): string;
ная с позиции Index.
s:=Copy('Borland Delphi',9,6);//Delphi
procedure Insert(Source: string; Вставляет в строку S cубстроку Source. Место
var S: string; Index: Integer);
вставки определяется Index.
function Length(S): Integer;
function Pos(Substr:
string): Integer;

string;

Возвращает длину строки S.
S: Возвращает индекс первого вхождения символа
Substr в строку S. Если такого символа в строке
нет, то результат равен нулю.

function AdjustLineBreaks(const S: Возвращает строку, заканчивающуюся символаstring): string;
ми CR/LF (возврат каретки/новая строка).
function
AnsiReverseString(const Возвращает реверсивное значение AText.
AText: AnsiString): AnsiString;

Таблица 3.7. Шаблоны форматирования, применяемые в функциях Format()
Спецификатор

Описание

%

Обязательный признак начала спецификатора формата

[index ":"]

Индекс аргумента

["-"]

Выравнивание влево

[width]

Ширина

["." prec]

Точность представления вещественного числа

type

Символ типа преобразования (см. табл. 3.8)

Таблица 3.8. Символы типа преобразования функции Format()
type Описание
d

Десятичный тип. Аргумент должен быть целым числом.

u

Десятичный тип без знака. Аналогичен типу d, но используется только с неотрицательными числами.

s:=Format('Целое число %d',[15]); //Целое число 15

64

Глава 3. Базовые функции Delphi

Таблица 3.8 (продолжение)
type Описание
e

Научный тип. Аргумент должен быть числом с плавающей точкой. Аргумент
преобразуется в строку вида "-d.ddd...E+ddd" (характеристика и мантисса).
Точность (по умолчанию) составляет 15 знаков после запятой.
s:=Format('1/3= %e',[1/3]);
// 1/3=3,3333333333333333E-01
Можно изменить точность с помощью поля prec:
s:=Format('1/3=%1.4e',[1/3]); // 1/3=3,333E-01

f

Фиксированный тип. Аргумент должен быть числом с плавающей точкой.
Значение аргумента преобразуется к виду "-ddd.ddd...". По умолчанию округление производится до второго знака после запятой.
s:=Format('1/3=%f',[1/3]);
// 1/3=0,33

g

Основной тип. Аргумент должен быть числом с плавающей точкой. Задача основного типа – преобразовать значение аргумента в самую короткую текстовую строку. Исходя из этого критерия, будет использован фиксированный или
научный формат.

n

Числовой тип. Аргумент должен быть числом с плавающей точкой. Значение
аргумента преобразуется к виду "-d ddd,ddd ddd...". Между тысячами ставится разделитель. По умолчанию округление производится до второго знака после запятой.
s:=Format('1E3*1E3=%n',[1E3*1E3]);

m

//1E3*1E3=1 000 000,00

Денежный тип. Аргумент должен быть числом с плавающей точкой. Значение
аргумента преобразуется в текстовую строку в виде денежной суммы. Вид результата определяется значениями глобальных переменных CurrencyString,
CurrencyFormat, NegCurrFormat, ThousandSeparator, DecimalSeparator и CurrencyDecimals. По умолчанию округление производится до второго знака после запятой.
s:=Format('Сумма = %m',[251.616+345.44]);

// Сумма = 597,06р.

p

Указатель. Аргумент должен быть указателем. Значение аргумента конвертируется в 8-ми символьную текстовую строку шестнадцатеричных цифр.

s

Строковый тип. Тип аргумента: string, PChar или Char. Допускается применять
совместно с полем prec, определяющим максимальную длину строки.
s:=Format('%.5s',['0123456789ABCDE']);
// 01234

x

Шестнадцатеричный тип. Аргумент должен быть целым числом. Значение аргумента преобразуется в строку шестнадцатеричных цифр.
s:=Format('24=%x',[26]);
// 24=1А

Таблица 3.9. Спецификаторы формата функций FormatFloat() и FormatCurr()
Спецификатор Описание
0

Обязательное цифровое поле. Если преобразуемая величина имеет
в этой позиции цифру, то вставится цифра. Иначе в поле будет
вставлен «0» .
s:=FormatFloat('0.0',9.1989); // 9.2

65

Процедуры и функции для работы со строками типа PChar

Спецификатор Описание
#

Необязательное цифровое поле. Если преобразуемая величина имеет в этой позиции цифру, то вставится цифра. Иначе поле останется
пустым.
s:=FormatFloat('0.0#####',9.1989);

// 9.1989

.

Десятичная точка. Разделитель целой и дробной части числа. Символ разделителя определяется в глобальной переменной DecimalSeparator.

,

Разделитель тысяч. Разделяет число влево от десятичной точки
на группы по три символа. Символ разделителя определяется
в глобальной переменной ThousandSeparator.

E+
Ee+
e-

Научный формат. Число преобразуется в научный формат. Количество нулей после спецификатора определяет ширину мантиссы:

'xx'/"xx"

Символы пользователя. В кавычки заключаются символы пользователя.

;

Разделитель представления положительных, отрицательных и нулевых чисел. Например: #,##0.00;(#,##0.00).

s:=FormatFloat('#,##0.0',91989.111); // 91 989.1

s:=FormatFloat('0.0##E+00',91989.111); // 9,199E+04
s:=FormatFloat('0.0##E-00',91989.111); // 9,199E04

s:=FormatFloat('"Число="0.0',9.198);

// Число=9.2

Процедуры и функции
для работы со строками типа PChar
Функции для работы со строками, заканчивающимися нулем, незаменимы
при организации взаимодействия приложения с функциями Windows API.
Все функции находятся в модуле SysUtils.
Таблица 3.10. Функции для работы со строками типа PChar
Функция
Описание
Создание и уничтожение строк
function StrAlloc(Size: Резервирует буфер для строки с нулевым символом в конце
Cardinal): PChar;
в куче памяти и возвращает указатель на первый символ.
После использования вызывайте функцию StrDispose().
function
StrNew(const Размещает копию строки Str в куче памяти и возвращает
Str: PChar): PChar;
указатель на нее. Размер в памяти равен StrLen(Str) + 5 байт.
После использования вызывайте функцию StrDispose().
procedure
StrDis- Уничтожает строку, созданную StrAlloc() или StrNew().
pose(Str: PChar);
function
StrBuf- Возвращает максимальное количество символов, которое
Size(const Str: PChar): может быть сохранено в буфере, зарезервированном функCardinal;
циями StrAlloc() или StrNew().

66

Глава 3. Базовые функции Delphi

Таблица 3.10 (продолжение)
Функция
Описание
Объединение и копирование строк
function StrCat(Dest: PChar; Объединяет две строки, дописывая Source в конец
const Source: PChar): PChar;
Dest. Возвращает указатель на строку Dest. Размер
буфера должен быть не менее StrLen(Dest) +
StrLen(Source) + 1 символов.
function StrLCat(Dest: PChar; Так же, как и StrCat, объединяет две строки, дописыconst Source: PChar; MaxLen: вая Source в конец Dest, но с учетом максимальной
Cardinal): PChar;
длины MaxLen результирующей строки. Другими словами, копирует не более MaxLen – StrLen(Dest) символов из строки Source.
function StrCopy(Dest: PChar; Копирует строку Source в Dest и возвращает указатель
const Source: PChar): PChar;
на Dest. Размер буфера должен быть не менее
StrLen(Source) + 1 символов.
function StrLCopy(Dest: PChar; Так же, как и StrCopy(), копирует строку Source в Dest
const Source: PChar; MaxLen: и возвращает указатель на Dest, но с проверкой макCardinal): PChar;
симально доступного размера.
function StrECopy(Dest: PChar; Копирует строку Source в Dest и возвращает указатель
const Source: PChar): PChar;
на последний нулевой символ. Размер буфера должен
быть не менее StrLen(Source) + 1 символов.
function StrPCopy(Dest: PChar; Копирует строку Pascal в строку Dest, заканчиваюconst Source: string): PChar; щуюся нулем. Возвращает указатель на строку Dest.
Размер буфера должен быть не менее Length(Source) + 1
символов.
function
StrPLCopy(Dest: Копирует MaxLen символов из строки Pascal в строку
PChar; const Source: string; Dest, заканчивающуюся нулем. Возвращает указаMaxLen: Cardinal): PChar;
тель на строку Dest.
function StrMove(Dest: PChar; Копирует Count символов из строки Source в строку
const Source: PChar; Count: Dest. Возвращает указатель на строку Dest.
Cardinal): PChar;
Форматирование строк
function StrLower(Str: PChar): Преобразует строку к нижнему регистру.
PChar;
function StrUpper(Str: PChar): Преобразует строку к верхнему регистру.
PChar;
function StrFmt(Buffer, For- Форматирование одного или нескольких значений
mat: PChar; const Args: array массива Args в буфере Buffer осуществляется в соотof const): PChar;
ветствии с параметром Format. Функция возвращает
указатель на буфер.
function
StrLFmt(Buffer: То же, что и StrFmt, но длина результата форматироPChar; MaxLen: Cardinal; For- вания ограничивается значением MaxLen.
mat: PChar; const Args: array
of const): PChar;

Процедуры и функции для работы со строками типа PChar

67

Функция

Описание

function FormatBuf(var Buffer; BufLen: Cardinal; const
Format;
FmtLen:
Cardinal;
const Args: array of const):
Cardinal;

Форматирует группу аргументов из массива Args. Результат помещается в Buffer (размер BufLen). Место
вставки аргументов и их форматирование определяются в строке Format. Функция возвращает количество символов, помещенных в Buffer.

Сравнение строк
function StrComp(const
Str2 : PChar): Integer;

Str1, Посимвольно сравнивает две строки с учетом регистра. Если строки идентичны, возвращает 0.

function StrIComp(const Str1, То же, что и StrComp(), но без учета регистра.
Str2:PChar): Integer;
function StrLComp(const Str1, Посимвольно сравнивает две строки, начиная с перStr2: PChar; MaxLen: Cardi- вого символа до символа с порядковым номером Maxnal): Integer;
Len. Если строки идентичны, то возвращает 0.
function StrLIComp(const Str1, То же, что и StrLComp(), но без учета регистра симвоStr2: PChar; MaxLen: Cardi- лов.
nal): Integer;
Поиск вхождения строки или символа в строку
function StrPos(const
Str2: PChar): PChar;

Str1, Возвращает указатель на место вхождения строки
Str2 в Str1. Если Str1 не содержит Str2, то возвращается nil.

function StrScan(const Str: Возвращает указатель на первое вхождение символа
PChar; Chr: Char): PChar;
Chr в строку Str. Если вхождений нет, возвращает nil.
function StrRScan(const Str: Возвращает указатель на последнее вхождение симPChar; Chr: Char): PChar;
вола Chr в строку Str. Если вхождений нет, возвращает nil.
Другие функции
function StrLen(const
PChar): Cardinal;

Str: Возвращает длину строки без учета завершающего
нулевого символа.

function StrEnd(const
PChar): PChar;

Str: Возвращает указатель на завершающий нулевой
символ строки Str.

Примечание: Для работы со строками с учетом языкового драйвера предусмотрены функции, аналогичные рассмотренным в таблице. Это функции
ANSI: AnsiStrPos() – аналог StrPos(), AnsiStrScan() – аналог StrScan() и т. д.
procedure . . .;
var Source : string; Dest : PChar;
begin
Source:='ABCDEFG';
Dest := StrNew(PChar('0123456789'));
StrPLCopy(PChar(Dest), Source, 5);
Application.MessageBox(Dest,'Результат', MB_OK);
StrDispose(Dest);
end;

68

Глава 3. Базовые функции Delphi

Работа с памятью
Приложение, стартующее под управлением 32-разрядной версии Windows,
получает в аренду 232 байт (4 Гбайт) виртуального адресного пространства,
причем совсем не обязательно оснащать компьютер 4 гигабайтами дорогостоящего ОЗУ. В тот момент, когда для работы программы оперативной памяти окажется недостаточно, операционная система воспользуется услугами файла подкачки и перебросит на него часть информации. Именно поэтому мы говорим не о физической, а о виртуальной памяти. Каким образом
Windows распределяет ячейки памяти между операционной системой и десятками клиентских приложений – достаточно сложный вопрос, который
мы пока затрагивать не будет. Мы лишь познакомимся с рядом ключевых
методов языка программирования Delphi, предназначенных для резервирования области памяти под наши данные.
Куча – это участок памяти, предназначенный для хранения данных изменяемой размерности.

Если в двух словах, то… в общем-то ничего сложного. И если вы не боитесь
маленьких неприятностей, то можете попробовать немного поэкспериментировать с памятью. Но имейте в виду, что эксперименты окажутся по-настоящему увлекательными, а последствия (если вы только начинаете программировать) – попросту непредсказуемыми.
Таблица 3.11. Функции для работы с памятью
Функция

Модуль Описание

procedure New(var P: Pointer);

System

Создает новую динамическую переменную и устанавливает указатель на нее.

procedure Dispose(var P: Point- System
er);

Очищает блок памяти, выделенный под
динамическую переменную.

procedure GetMem(var P: Pointer; System
Size: Integer);

Создает динамическую переменную и указатель P на блок памяти.

procedure FreeMem(var P: Point- System
er[; Size: Integer]);

Освобождает блок памяти, связанный
с указателем P.

function AllocMem(Size:
nal): Pointer;

Cardi- Sysutils Выделяет блок размером Size в куче памяти, обнуляет его и возвращает указатель.

function GetHeapStatus: THeapSta- System, Возвращает текущий статус менеджера
tus;
ShareMem памяти. Данные будут размещены в записи THeapStatus.
procedure
GetMemoryManager(var System
MemMgr: TMemoryManager);

Указывает на точки входа действующего
в настоящее время менеджера памяти.
Информация окажется в MemMgr().

procedure SetMemoryManager(const System
MemMgr: TMemoryManager);

Устанавливает точки входа функциям,
инкапсулированным в менеджере памяти.

Процедуры управления ходом выполнения программы

Функция

69

Модуль Описание
Изменения окажут влияние на поведение
стандартных процедур: GetMem(), ReallocMem() и FreeMem().

function
Boolean;

IsMemoryManagerSet

procedure
ReallocMem(var
Pointer; Size: Integer);

: System

Указывает, был ли изменен стандартный
менеджер памяти с помощью функции
SetMemoryManager().

P: System

Перераспределяет динамическую переменную, распределенную ранее функциями GetMem(), AllocMem() или ReallocMem().

function SysFreeMem(P: Pointer): System Освобождает блок памяти, на который
Integer;
или
указывает Pointer.
ShareMem
function SysGetMem(Size:
ger): Pointer;

Inte- System Выделяет Size байт и возвращает указатель на них. Если модуль ShareMem не исили
ShareMem пользуется, то блок выделяется в глобальной куче.

function SysReallocMem(P: Point- System Возвращает указатель на Size байт, при
этом значение P сохраняется.
er; Size: Integer): Pointer;
или
ShareMem
function
CompareMem(P1,
P2: Sysutils Сравнивает содержимое двух блоков памяти по адресу P1 и P2 длиной Length байт.
Pointer; Length: Integer): BooВозвращает true в случае полного соотlean; assembler;
ветствия.

Модуль ShareMem обеспечивает подключение менеджера памяти, позволяющего разделять память между двумя и более процессами. Этот менеджер содержится в динамической библиотеке borlndmm.dll. Учтите это при установке созданных вами
приложений на другие компьютеры, т. к. без этой библиотеки приложение, обращающееся к модулю ShareMem, не сможет работать.

Процедуры управления ходом
выполнения программы
В модуле System представлены шесть процедур, предназначенных для управления ходом выполнения программы. Большая часть этих методов принудительно прекращает выполнение циклов, методов или даже программ.
Таблица 3.12. Процедуры управления программой
Метод

Описание

procedure Abort;

Экстренное прекращение выполнения метода без вызова исключительной ситуации.

procedure Break;

Прекращает выполнение цикла.

70

Глава 3. Базовые функции Delphi

Таблица 3.12 (продолжение)
Метод

Описание

procedure Continue;

Применяется внутри циклов. Вызов метода принудительно заставляет цикл прекратить выполнение текущего шага и перейти к следующей итерации.

procedure Exit;

Метод предназначен для безусловного выхода из процедуры или функции.

procedure Halt [( Exitcode: Экстренно прекращает выполнение программы. ДопусInteger) ];
кает передачу кода выхода в параметре Exitcode. Старайтесь избегать вызова этого метода, т. к. он не гарантирует нормального уничтожения приложения.
procedure RunError [( Error- Прекращает выполнение программы и генерирует
code: Byte ) ];
ошибку выполнения приложения. Допускает явное
указание кода ошибки в параметре Errorcode.

Разные функции
Таблица 3.13. Разные функции
Функция

Описание

procedure Assert(expr : Boo- Тестовая процедура, предназначенная для отладки
lean [; const msg: string]); проекта. Если логическое выражение expr равно true,
то ничего не происходит. Если false, то выводится сообщение msg.
function Assigned(var P): Boo- Тест на наличие объекта по указателю P. Если по заlean;
данному адресу объект отсутствует (nil), то возвращается значение false.
procedure Beep;

Генерирует звуковой сигнал системы.

function Chr(X: Byte): Char;

Возвращает символ таблицы ASCII, соответствующий
значению X.

procedure
FillChar(var
X; Помещает значение Value в заданное друг за другом
Count: Integer; Value: Byte); количество байт длиной Count
var S: string[10];
begin
FillChar(S, SizeOf(S), '!');
end;
procedure FreeAndNil(var Obj); Освобождает объектную ссылку и заменяет ссылку на
нуль. Данную процедуру можно использовать только
с TObject и его потомками.
function Hi(X): Byte;

Возвращает содержимое старшего байта аргумента Х.

function Lo(X): Byte;

Возвращает содержимое младшего байта аргумента Х.

procedure Move(const Source; Копирует Count байт из Source в Dest.
var Dest; Count: Integer);

71

Резюме

Функция

Описание

function SizeOf(X): Integer;

Возвращает количество байт, занимаемое X.

function Slicevar A: array; Возвращает часть массива от первого значения до знаCount: Integer): array;
чения Count.

Резюме
Язык Object Pascal и его логическое развитие Delphi обладают тысячами
процедур и функций. Безусловно, их запоминание – дело, заведомо обреченное на провал, но программист по крайней мере должен знать о существовании той или иной функции и уметь ее найти. Надо помнить, что к любому
проекту Delphi автоматически подключается модуль System, в котором спрятаны используемые компилятором Delphi подпрограммы низкого уровня.
Наиболее часто используемые процедуры расположены в модуле SysUtils.
Кроме того, в нем описаны наиболее общие классы обработки исключительных ситуаций и утилиты по работе со строками, датами и временем. Расширенную математическую поддержку предоставляет модуль Math.

4
Основы работы с файлами
Трудно переоценить умение работать с файлами, ведь это одна из наиболее
часто встречающихся в программировании задач. По большому счету можно
утверждать, что вся работа компьютера сводится к манипуляциям с файлами, а точнее данными, содержащимися в них. Практически любая создаваемая программа (кстати, тоже как минимум состоящая из одного файла)
должна взаимодействовать с файловой системой компьютера и, более того,
осуществлять базовые операции ввода-вывода (I/O routines).
Что понимается под операциями ввода-вывода? Это действия, связанные с созданием нового или открытием существующего файла, с операциями чтения
из файла и записью в него информации, с копированием, перемещением и
удалением файла и т. д. Кроме того, в системе Windows умение работать
с файлами весьма пригодится при обращении к именованным каналам, почтовым слотам и сокетам.
Данная глава посвящена основным приемам работы с файлами и файловой
системой. Прежде чем перейти к основному материалу, стоит отметить, что
в библиотеке визуальных компонентов VCL среды программирования Delphi
реализован целый спектр компонентов, значительно упрощающих организацию доступа к каталогам и файлам (см. главу 14 «Диалог с Windows»). Вместе с тем ряд элементов управления инкапсулирует методы, предоставляющие программисту достаточно совершенный механизм загрузки и сохранения данных. К ним относятся мемо-поле TMemo, расширенный текстовый редактор TRichEdit, рисунок TImage, списки TListBox и TComboBox и еще достаточно
обширный перечень компонентов, изучаемых в дальнейших главах.
Поскольку для реализации базовых операций ввода-вывода нет необходимости в использовании библиотеки VCL, то все примеры данной главы написаны на «чистом
Паскале» c расчетом на использование в консольных приложениях.

73

Классификация типов файлов

Классификация типов файлов
Прежде чем приступить к изучению базовых операций ввода-вывода, немного времени посвятим вопросу систематизации типов файлов. Программисты, работающие на языке Pascal (впрочем, как и приверженцы подавляющего большинства других языков программирования), все существующие
типы файлов разделяют на три направления:
1. Текстовые файлы.
2. Типизированные файлы.
3. Двоичные (нетипизированные) файлы.
Как следует из названия, текстовый файл специализируется на хранении
текстовой информации, представленной в символах ASCII. Как правило, такие файлы снабжаются специфичным только для них расширением .txt и могут быть открыты любым текстовым редактором, начиная с простейшего
Блокнота и заканчивая популярным текстовым процессором Microsoft Word.
Работа с любым типом файла в языке Pascal требует определения так называемой файловой переменной, которая будет выступать в качестве аргумента во многих методах ввода-вывода. Способ объявления файловой переменной определяется типом файла, на который будет указывать наша переменная. Например, подготовка переменной для работы с текстовым файлом выглядит следующим образом:
var MyFile : TextFile;

В отличие от узкоспециализированного текстового файла, типизированный
файл предназначен для работы с данными, определяемыми программистом.
В качестве них могут выступать как любые целые, вещественные, символьные, логические и строковые типы данных, так и записи, состоящие из только что перечисленных типов.
var F : File of <тип файла>;

Приведем пример объявления трех файловых переменных, нацеленных на работу c файлами целых чисел, вещественных чисел и записей соответственно.
var Int_File
: file of Integer;
Real_File : file of Real;
Record_File : file of TPoint;

//файл типа Integer
//файл типа Real
//файл для работы с записями типа TPoint

Синтаксис языка Pascal не запрещает создание типизированных файловмассивов, хотя в общем-то файл сам по себе является массивом.
Если в определении типизированного файла используются строковые данные string,
то необходимо определить количество символов, которое планируется хранить в одной записи. Ограничение указывается в квадратных скобках после ключевого слова
string[xx].

Самый универсальный формат файла – двоичный. Это файлы графики, аудиои видеофайлы, электронные таблицы, HTML-файлы. Короче говоря, все су-

74

Глава 4. Основы работы с файлами

ществующие файлы. Как видите, текстовые и типизированные файлы представляют собой частный случай двоичного файла.
Для объявления файловой переменной двоичного файла применяют следующий синтаксис:
var DataFile: File;

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

Чтение и запись данных
Мы уже знаем, что базовые операции ввода-вывода (Input-Output, I/O) в первую очередь нацелены на решение задач чтения из файла и записи в файл
требуемых данных. При реализации этих операций любой язык программирования требует от нас особой внимательности и осторожности. Во-первых,
следует учитывать, что перечень применяемых методов ввода-вывода определяется типом файла, с которым планируется работа. Во-вторых, необходимо соблюдать строго определенную последовательность вызова методов.
В-третьих, в своем коде программист должен предусмотреть и тщательно
реализовать систему обработки потенциальных ошибок.
Предлагаю вашему вниманию стандартный каркас исходного кода по работе
с файлом, на который рекомендуется опираться вне зависимости от типа
файла.
var F : file;
begin
AssignFile(F,'C:\MyFile.dat'); // Связывание файловой переменной F с файлом
try
//Секция для потенциально опасных операций над файлом
finally
CloseFile(F);
// Закрытие файла и освобождение переменной
end;
end.

Прокомментирую эти строки. Нами объявлена файловая переменная с именем F. Первая процедура в листинге предназначена для связывания файловой переменной F с именем файла FileName. Эту процедуру дальше будем называть процедурой инициализации базовых операций ввода-вывода.
procedure AssignFile(var F; FileName: string);

Теперь файловая переменная в состоянии держать связь с этим файлом до
тех пор, пока не будет освобождена. Для разрыва связи и освобождения занятых ресурсов необходим вызов процедуры закрытия файла:
procedure CloseFile(var F);

75

Классификация типов файлов

Указанный метод завершает операции ввода-вывода. Обе процедуры всегда
применяются в паре, и если в программе был осуществлен вызов AssignFile(), то в заключение позаботьтесь и об обязательном вызове CloseFile().
Однако при работе с файлом всегда высока вероятность появления целого
спектра самых разнообразных ошибок, которые могут привести к возникновению исключительной ситуации. Именно поэтому в примере процедура закрытия вынесена в секцию finally обработчика ошибки try..finally. Такой
подход гарантирует освобождение файловой переменной даже при возникновении ошибок. Более подробно особенности конструкции try..finally рассматриваются в главе 15.
Ни в коем случае не пытайтесь вызвать метод AssignFile() для файловой переменной, уже ссылающейся на другой открытый файл. При программировании всегда руководствуйтесь правилом: каждому файлу – индивидуальная файловая переменная.

Текстовые файлы
После того как мы связали файловую переменную с именем файла, в зависимости от поставленных задач можно пойти по одному из трех путей (см. алгоритм на рис. 4.1):
1. Создание нового тестового файла.
2. Открытие существующего файла в режиме только для чтения.
3. Открытие существующего файла с возможностью добавления новых строк.
AssignFile()

Нет

Открыть файл?

Нет

//создание файла
ReWrite()

Да

Да

Только чтение?

//добавить строки
Append()

//запись данных
WriteLn()

//читать строки
Reset()

//чтение данных
ReadLn()

CloseFile()

Рис. 4.1. Алгоритм работы с текстовым файлом

76

Глава 4. Основы работы с файлами

Для реализации этих задач в языке Pascal предусмотрено три процедуры:
Rewrite(), Reset() и Append(). Для создания нового файла вызовите метод:
procedure Rewrite(var F: File [; RecSize: Word ] );

Ключевой параметр процедуры – файловая переменная F. В результате выполнения метода формируется пустой файл с именем, определенным в процедуре инициализации AssignFile(). Если вдруг имя вновь создаваемого
файла совпадет с именем существующего, то старый файл стирается без всякого предупреждения и сожаления. Второй необязательный параметр определяет, какими порциями будут осуществляться операции записи данных
в файл. По умолчанию назначается размер, равный 128 байт. При работе
с текстовыми файлами этот параметр не используется, но играет важную
роль для двоичных файлов.
Для открытия уже существующего файла процедура Rewrite() не нужна.
Вместо нее воспользуйтесь методом инициализации чтения:
procedure Reset(var F [: File; RecSize: Word ] );

Параметры процедуры идентичны аргументам предыдущего метода. После
открытия внутренний указатель файла позиционируется на самом первом
его байте. При использовании метода Reset() надо иметь в виду, что режим
открытия файла этой процедурой зависит от значения глобальной переменной FileMode. По умолчанию в ней содержится значение 2, что соответствует
режиму, допускающему как чтение, так и запись. Если планируется работа
с файлом только в режиме чтения, то перед вызовом Reset() присвойте переменной FileMode значение 0. Соответственно, если файл открывается только
для записи, установите переменную в значение 1.
При работе с текстовыми файлами метод Reset() откроет файл в режиме только
для чтения независимо от значения переменной FileMode.

И наконец, текстовый файл может открываться в режиме добавления данных в конец файла. Для этого вызывается процедура:
procedure Append(var F: Text);

После выполнения процедуры внутренний указатель файла позиционируется на самом последнем байте файла.
Осталось привести несколько простых поясняющих примеров. Первый из них
создает новый файл в корне диска C:\ и заполняет его десятью символами A.
program DemoTxt1;
{$APPTYPE CONSOLE}
uses SysUtils;
var F : TextFile;
i : integer;
begin
AssignFile(F,'C:\NewFile.txt');
try

Классификация типов файлов

77

Rewrite(F);
for i:=0 to 9 do Write(F,'A');
finally
CloseFile(F);
end;
end.

Второй пример – скромная консольная программка, осуществляющая вывод содержимого текстового файла autoexec.bat с последующим отображением прочитанной информации на экран компьютера.
program DemoTxt2;
{$APPTYPE CONSOLE}
uses SysUtils;
var F : TextFile;
s : string;
begin
AssignFile(F,'C:\Autoexec.bat');
Reset(F);
TRY
while SeekEof(f)=False do //проверка местоположения указателя
begin
//пока указатель не достиг последней строки файла
ReadLn(F,S);
//считываем очередную строку в переменную S
WriteLn(s);
//и отображаем считанную информацию на экране
end;
FINALLY
CloseFile(F);
END;
ReadLn;
end.

Третий пример добавляет десять строк в конец текстового файла. Он также
не должен вызвать особых затруднений, только не забудьте перед запуском
программы создать в корне диска <C:> файл MyFile.txt, иначе программа
выразит свое недоумение по поводу отсутствия открываемого файла.
program DemoTxt3;
{$APPTYPE CONSOLE}
uses SysUtils;
var F : TextFile;
i : integer;
begin
AssignFile(F,'C:\MyFile.txt');
Append(F);
TRY
for i:=0 to 9 do WriteLn(F,'Строка -', i);
Flush(f);
FINALLY
CloseFile(F);
END;
ReadLn;
end.

78

Глава 4. Основы работы с файлами

Уверен, что в предложенных листингах внимательный читатель заметил
ряд новых методов. В первую очередь это процедуры, осуществляющие чтение Read() и запись Write() данных.
Процедуры Read() и Write() весьма часто применяют в консольных приложениях
для обычного взаимодействия с пользователем, т. к. последние способны работать не только с файлами, но и с клавиатурой и консолью.
program DemoCns;
{$APPTYPE CONSOLE}
uses SysUtils;
var s : string;
begin
Write('Insert name: ');
Readln(s);
Writeln('Name: ',s);
Writeln('Press any key to exit');
Readln;
end;
end.
procedure
procedure
procedure
procedure

Read( [ var F: Text; ] V1 [, V2,...,Vn ] );
ReadLn([ var F: Text; ] V1 [, V2,...,Vn]);
Write([ var F: Text; ] P1 [, P2,..., Pn]);
WriteLn([ var F: Text; ] P1 [, P2,...,Pn]);

Все четыре процедуры вооружены идентичными параметрами. Первый аргумент, F, соответствует файловой переменной текстового файла. В качестве второго аргумента в процедурах чтения указывают переменную, в которую осуществляется чтение данных из файла, а в процедурах записи, наоборот, данные, содержащиеся в этой переменной, записываются в файл. Обратите внимание на ряд необязательных параметров в квадратных скобках [, V2,...,Vn].
При желании программист может ими воспользоваться, как в примере добавления строк в конец файла, где помимо текстовой информации 'Строка -'
в файл записывается номер шага в цикле. Окончание …ln (от англ. line)
в имени процедуры говорит нам о том, что процедура нацелена на работу
со строками.
Очистка буфера текстового файла осуществляется методом:
procedure Flush (var F: Text);

Вызов этого метода целесообразен в том случае, когда файл был открыт для
записи. Функция гарантирует, что все данные из буфера будут сохранены
в файл на диске. Эта процедура вызывается по окончании операций записи
данных в файл.
Познакомимся с еще одной весьма полезной функцией с именем Eof() (сокращение от англ. End of file), в чьи обязанности входит проверка того, не
находится ли внутренний указатель файла на самой последней его строке.
Другими словами, не достигнут ли еще конец файла.
function Eof[ (var F: Text) ]: Boolean;

79

Классификация типов файлов

Этот метод обычно применяется при организации чтения данных из файла.
Вернитесь к листингу проекта, демонстрирующего способ чтения файла
autoexec.bat. Здесь метод Eof() вызывается внутри цикла while…do. Пока
функция возвращает значение false (признак того, что конец файла еще не
достигнут), продолжается выполнение операций чтения. Каждый вызов метода WriteLn() заставляет указатель файла перемещаться на одну строку вперед, все ближе и ближе к концу файла.
Специально для текстовых файлов реализован «продвинутый» метод проверки достижения конца файла:
function SeekEof[ (var F: Text) ]: Boolean;

Отличие метода SeekEof() от обычного Eof() в том, что он умеет распознавать
пустые строки (заполненные символами пробела) и исключать их из процесса чтения.
Раз мы заговорили о проверках на нахождение указателя в конце файла, то
стоит упомянуть тест на нахождение указателя в конце строки текстового
файла.
function EoLn[ (var F: Text) ]: Boolean;

Соответственно аналогичный метод, но игнорирующий пробелы:
function SeekEoLn[ (var F: Text) ]: Boolean;

Для лучшего понимания поведения методов SeekEof() и SeekEoLn() изучите
предлагаемый код. В примере предложен способ хранения целых чисел
в текстовом файле.
program DemoSeek;
{$APPTYPE CONSOLE}
uses SysUtils;
var f: TextFile;
i: Integer;
begin
AssignFile(f,'c:\space.txt');
TRY
//создание демонстрационного файла
Rewrite(f);
Writeln(f, ' 1 2 3 4 5 ');
Writeln(f, '
');

// целые числа, разделенные пробелами
// вносим пустые строки для демонстрации
// работы метода SeekEof

Writeln(f, ' ');
Writeln(f, ' 55 123 6 35 0
77');
//в файл записаны 4 строки
Reset(f); //переходим к операциям чтения
while SeekEof(f)=false do
begin
while SeekEoln(f)=false do

// цикл построчного чтения, игнорирующий
// пустые строки
// цикл чтения чисел из строки,
// игнорирующий пробелы

80

Глава 4. Основы работы с файлами
begin
Read(f, i);
Writeln(i);
end;
end;
FINALLY
CloseFile(f);
END;
Readln;
end.

Повторившие этот пример увидят, что, несмотря на наличие пустых строк
и пробелов между символами, программа выявила целые числа и вывела их
на экран в один столбец.

Типизированные файлы
Если информацию, которую вы рассчитываете хранить в виде файла, удобнее представить в табличном виде (например, ассортимент товаров в магазине, расписание самолетов или список студентов), то вместо текстового файла
целесообразно использовать типизированный файл. Организация работы
с типизированными файлами весьма схожа с только что изученными операциями для текстовых файлов.
Допустим, что в файле необходимо хранить информацию о сотрудниках
предприятия. Планируемый перечень данных включает фамилию, инициалы работника и его скромную зарплату – всего четыре позиции. В таком случае первый шаг разработчика – определить некоторую структуру данных
(в терминах Pascal – записи), способную содержать информацию. Для этой
цели опишем запись TPeople, затем при объявлении файловой переменной
укажем, что она предназначена для работы с типизированным файлом типа
TPeople.
program ...;
{$APPTYPE CONSOLE}
uses SysUtils;
type
TPeople = packed record
SurName : string[25];
FName,LName : CHAR;
Money : currency;
end;
var People: TPeople;
F
: File Of TPeople;
begin
AssignFile(F,'C:\People.dat'); {связывание файловой переменной с именем файла}
Rewrite(F);
//создание файла
TRY
{заполнение полей записи}
with People do
begin

Классификация типов файлов

81

SurName:='Петров';
People.FName:='В';
People.LName:='А';
People.Money:=1000;
end;
Write(F,People); //внесение записи
FINALLY
CloseFile(F);
//закрытие файла
END;
end.

Если в полях структуры используются строковые данные, то обязательно указывайте количество символов в строковом поле, иначе компилятор не сможет определить размер одной записи в файле.

Повторив пример создания типизированного файла, вы не увидели особых
отличий от аналогичной операции формирования файла текстового типа.
И в этом случае мы прошли через стандартную последовательность вызова
методов AssignFile(), Rewrite(), Write() и Close(), подробно прокомментированных в предыдущем разделе.
Несколько иначе обстоят дела в случаях редактирования типизированного
файла или чтения из него информации (см. алгоритм на рис. 4.2). Здесь нам
может понадобиться системная переменная FileMode, определяющая режим
открытия файла (по умолчанию ее значение равно 2, что предоставляет право для чтения и записи одновременно).
Для открытия файла воспользуемся уже известным нам методом Reset() и…
Настал черед завести знакомство с весьма полезной процедурой, отвечающей за позиционирование внутреннего указателя файла:
procedure Seek(var F; N: Longint);

Первый аргумент данного метода (как, впрочем, и всех других методов ввода-вывода) требует передачи файловой переменной. Второй параметр определяет, в каком месте файла мы собираемся оказаться.
Допустим, что мы рассчитываем поместить указатель в конец файла, тогда
наберем следующую строку кода:
Seek(F,FileSize(F)-1); // FileSize() возвращает количество записей
// в типизированном файле

При желании поместить указатель в начале файла надо набрать:
Seek(F,0);

а теперь переместим указатель ровно в середину файла:
Seek(F,FileSize(F) div 2);

На законный вопрос: «А как узнать, в каком месте находится указатель
в настоящий момент?» нам ответит еще один метод:
function FilePos(var F): Longint;

82

Глава 4. Основы работы с файлами

AssignFile()

Нет

Открыть файл?

Нет

Да

Только чтение?

//только чтение
FileMode:=0

//только запись
FileMode:=1
//запись-чтение
FileMode:=2
//создание файла
ReWrite()

Да

Reset()

Reset()

//позиция
Seek()

//позиция
Seek()

//запись данных
Write()

//чтение данных
Read()

CloseFile()

Рис. 4.2. Алгоритм работы с типизированным файлом

И если указатель ссылается на начало файла, то функция вернет нулевое
значение, а если на конец, то возвратит значение FileSize(). Следующий метод сообщает о количестве записей в типизированном файле:
function FileSize(var F): Integer;

Очень важно помнить, что функция определения размера файла возвращает размер
не в байтах, а в элементах, из которых состоит файл. В нашем случае элементом
файла является запись TPeople, размер которой равен 36 байт. Поэтому, чтобы
выяснить физический размер типизированного файла в байтах, необходимо умножить значение FileSize(F) (возвращающее количество записей в файле) на размер этой записи в байтах – SizeOf(TPeople).
То же утверждение в полной мере относится к методам FilePos() и Seek(): и у них
в качестве единицы измерения выступает не байт, а номер элемента файла.
Методы FileSize() и FilePos() не могут вызываться при работе с текстовыми
файлами.

Классификация типов файлов

83

Следующий листинг демонстрирует еще одно важное преимущество типизированного файла – возможность редактировать любой из элементов файла.
Вспомните, что в обычных текстовых файлах мы можем лишь добавлять
строки, а здесь приобретаем бесценную возможность вносить изменения в любую запись.
program ...;
{$APPTYPE CONSOLE}
uses SysUtils;
type TPeople = packed record
SurName : string[25];
FName, LName : CHAR;
Money : currency;
end;
var People : TPeople;
F
: File Of TPeople;
begin
AssignFile(F,'c:\People.dat');
FileMode:=1; Reset(F); //открываем файл в режиме «только для записи»
TRY
with People do
begin
SurName:='Иванов';
People.FName:='С';
People.LName:='Г';
People.Money:=999.5;
end;
Seek(F,0);
//позиционируем указатель на первый элемент файла
Write(F,People);
//вносим изменения в файл
FINALLY
CloseFile(F);
END;
end.

Вот как должен выглядеть листинг программы, если вдруг потребуется
удвоить оклады всем сотрудникам:
AssignFile(F,'c:\People.dat');
FileMode:=2;
Reset(F);
TRY
while FilePos(F)<>FileSize(F) do
begin
Read(F,People);
People.Money:= People.Money*2;
Seek(F, FilePos(F)-1);
Write(F,People);
end;
FINALLY
CloseFile(F);
END;

84

Глава 4. Основы работы с файлами

Несколько сложнее обстоят дела с удалением из типизированного файла ненужного элемента. Однако при должной сноровке и эта задача вполне разрешима. Предположим, что за какую-то провинность (например, несанкционированное повышение зарплаты всем сотрудникам) из списка надо вычеркнуть бухгалтера Петрова. Для реализации этой карательной акции нам
потребуются две файловые переменные: Source и Receptor. С помощью Source
мы присоединимся к исходному файлу и просто переберем все его элементы,
проверяя при этом, не является ли текущий элемент записью о нашалившем
счетоводе Петрове. Вторая файловая переменная Receptor нацелена на создание файла-получателя. В этот файл мы переносим все элементы исходного
файла, успешно избежавшие фильтрации (с фамилиями, отличными от
«Петров»).
var People: TPeople;
Source, Receptor : File Of TPeople;
begin
AssignFile(Source,'c:\People.dat');
TRY
Rename(Source, 'c:\~People.dat');
Reset(Source);
AssignFile(Receptor,'c:\People.dat');
Rewrite(Receptor);
while FilePos(Source)<>FileSize(Source) do

// переименовываем старый файл
// подключаемся к старому файлу

// цикл перебора элементов
// старого файла

begin
Read(Source,People);
if People.SurName<>'Петров' then Write(Receptor,People);
end;
FINALLY
CloseFile(Source);
Erase(Source); //удаляем старый файл
CloseFile(Receptor);
END;
end.

В рассмотренном листинге мы использовали еще две новые процедуры. Первая из них предназначена для переименования файла F. Новое имя надо указать в параметре NewName.
procedure Rename(var F; Newname: string);

Для удаления файла нам понадобится метод Erase().
procedure Erase(var F);

Прежде чем удалить файл, определенный в файловой переменной F, обязательно закройте его, вызвав метод CloseFile().

Если типизированный файл включает сотни (и более) записей, предложенный выше алгоритм «борьбы с лишними сотрудниками» становится весьма
неэффективным, в особенности если за один сеанс работы с файлом удаляет-

Классификация типов файлов

85

ся несколько записей. Ведь мы ради каждой удаляемой строки вынуждены
заново перезаписывать достаточно большой файл, а это весьма ресурсоемкая
и, главное, продолжительная операция.
В таких случаях программисты идут на хитрость: в состав полей записи типизированного файла добавляется еще одно поле логического типа, например Deleted, в котором хранится признак удаления. При поступлении команды на удаление записи вместо реального стирания строки полю Deleted присваивается значение true и запись убирается с экрана. На все это уходят тысячные доли секунды, а реальное удаление строк выполняется (в тайне от
всех) в момент закрытия программы.
Весьма похожий алгоритм удаления строк с успехом работает в базах данных dBase и Paradox. Более того, в этих таблицах строки вообще никогда не
удаляются, а просто скрываются. Правда, это сопряжено с одним недостатком: файлы таблиц постоянно увеличиваются в размерах. Поэтому изредка
их требуется упаковывать с помощью специальных подпрограмм.

Двоичные файлы
Подавляющее большинство файлов, хранящихся на жестких дисках компьютеров, не являются типизированными. С точки зрения языка Pascal они не
имеют строгого формата. Точнее говоря, в их формате разбираются только узкоспециальные программы. Например, программы Adobe PageMaker и Microsoft Word (по сути выполняющие аналогичные задачи – редактирование
текста) работают с весьма несхожими файлами. Дело в том, что в их файлах
помимо текста хранится обширная служебная информация: гарнитура,
кегль и цвет шрифта, трекинг, интерлиньяж и многое другое. Разработчики
двух разных программных продуктов для каждого из пакетов сформировали свой формат файла и вполне довольны полученными результатами. Возможно, и вы создадите свой тип файла, однако в нашей классификации он
все равно будет именоваться двоичным.
Отличие состава базовых операций ввода-вывода, применяемых для нетипизированных файлов, заключается в методах, осуществляющих чтение и запись. Суть отличия от уже известных вам процедур Read() и Write() заключается в том, что чтение и запись производятся блоками.
procedure BlockRead(var F: File; var Buf; Count: Integer [; var AmtTransferred:
Integer]);
procedure BlockWrite(var F: File; var Buf; Count: Integer [; var AmtTransferred:
Integer]);

С первым аргументом F все ясно: это файловая переменная. Второй аргумент
Buf служит буфером, в который записываются прочитанные данные или, наоборот, из которой считывается информация для сохранения в файле. Параметр Count определяет, сколько записей планируется считать из файла за одну операцию чтения. Как правило, ему присваивают значение, равное 1. Последний аргумент, AmtTransferred, необязательный. Во время операций чтения из него можно узнать количество записей, реально считанных из файла,

86

Глава 4. Основы работы с файлами

а во время осуществления записи в переменной окажется количество записей, внесенных в файл.
Внимательный читатель наверняка уже заинтересовался: «Что мы имеем
в виду, когда применяем термин запись при работе с нетипизированным
файлом?» Действительно, здесь требуется внести некоторую ясность. Еще
раз взгляните на объявление методов BlockRead() и BlockWrite(). В этих процедурах не определен тип аргумента Buf, что так не похоже на строгий язык
Pascal. Но каким образом можно определить тип данных для работы с файлом, который по определению нетипизирован? Поэтому в качестве буфера
обычно используют обычный байтовый массив:
var Buf : array [0..127] of byte;

Размер массива устанавливается программистом. Вот про этот размер и говорят – размер записи, а сам буфер часто именуют записью. По умолчанию размер буфера назначают равным 128 байт. Если этот размер не устраивает, его
можно изменить под свою задачу. Однако при этом надо помнить о втором параметре процедур Reset() и Rewrite(), который определяет размер блока. Если
есть необходимость, перечитайте раздел, посвященный текстовым файлам.
Размер записи всегда должен быть кратен размеру файла, с которым собираетесь
работать. В противном случае вы столкнетесь с проблемой чтения за пределами
файла. Самым универсальным размером записи служит 1 байт, однако такое значение снизит производительность процедур чтения-записи.

Как говорится, лучше один раз увидеть. Взгляните на листинг, демонстрирующий способ создания двоичного файла.
program DemoBinFl;
{$APPTYPE CONSOLE}
uses SysUtils;
var F : File;
Buffer : array[0..1023] of byte;
I : integer;
begin
for i:=0 to SizeOf(Buffer) do Buffer[i]:=RANDOM(255);
AssignFile(F,'c:\binfile.dat');
TRY
Rewrite(F,1024);
BlockWrite(F,Buffer,1);
FINALLY
CloseFile(F);
END;
end.

В качестве буфера используется массив размером 1 Кбайт. Заполнив массив
псевдослучайными числами, переносим содержимое его ячеек на жесткий
диск.
Следующий листинг демонстрирует процесс чтения из нетипизированного
файла. Пример рассчитан на то, что размер загружаемого файла кратен
256 байт.

87

Классификация типов файлов
program DemoBinRead;
{$APPTYPE CONSOLE}
{Чтение из двоичного файла}
uses SysUtils;
var F : File;
Buffer: array[0..255] of byte;
i : integer;
begin
AssignFile(F,'c:\binfile.dat');
TRY
Reset(F,256);
while EOF(F)=False do
begin
BlockRead(F,Buffer,1);
//прочие операции
end;
FINALLY
CloseFile(F);
END;
end.

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

Операция

Описание

100

Disk read error

Read

Попытка чтения за пределами
файла.

101

Disk write error

CloseFile,
WriteIn, Flush

102

File not assigned

Reset, Rewrite, Append, В файловой переменной нет
Rename, Erase
ссылки на файл. Например, не
был вызван метод AssignFile().

103

File not open

CloseFile, Read, Write, Файл не был открыт.
Seek,
Eof,
FilePos,
FileSize, Flush, BlockRead, BlockWrite

104

File not open for Read, Readln, Eof, Eoln, Файл открыт не в режиме для
SeekEof, SeekEoln
input
чтения.

Write, Ошибка записи, например при
нехватке места на диске.

88

Глава 4. Основы работы с файлами

Достаточно опытный программист может позволить себе некоторую вольность и отключить реакцию Delphi на ошибки ввода-вывода. Для этого в листинге перед процедурами работы с файлами укажите соответствующую директиву компилятору {$I-}, но в этом случае обработка ошибок перекладывается на вас. Не забудьте все вернуть в исходное состояние, включив «обесточенную» опцию {$I+}. Код возможной ошибки возвратит метод:
function IOResult: Integer;

Если же все завершилось удачно, то результатом окажется ноль. Продемонстрируем это на примере. Взгляните на следующие абсолютно стандартные
строки кода:
var F : File;
Buffer : array[0..255] of byte;
begin
AssignFile(F,'c:\test.tst');
TRY
Reset(f,255);
BlockRead(F,Buffer,1);
FINALLY
CloseFile(f);
END;
end.

Однако вполне работоспособный код откажется работать, если в корне диска
C:\ не окажется тестового файла с названием test.tst. В таком случае на экране компьютера появится сообщение, информирующее нас о таком прискорбном факте. А теперь применим наши знания относительно обработки
ошибок ввода-вывода и напишем более жизнестойкие строки.

Рис. 4.3. Сообщение отладчика об отсутствующем файле
var F : File;
Buffer : array[0..255] of byte;
begin
AssignFile(F,'c:\test.tst');
{$I-}
Reset(f,255);
if IOResult=0 then
begin
BlockRead(F,Buffer,1);
CloseFile(f);
end
else WriteLn('Sorry! File not found!');

Низкоуровневые методы работы с файлами

89

{$I+}
ReadLn;
end.

Низкоуровневые методы работы с файлами
Операционная система Windows осуществляет работу с файлами при помощи дескрипторов – указателей на файл как на физическое устройство. В составе Delphi предусмотрен специальный набор функций для работы с файлами в стиле Windows (табл. 4.2).
Таблица 4.2. Функции, работающие с файлами в стиле Windows
Метод

Описание

function
FileCreate(const Создает файл с именем FileName и возвращает указаFileName: string): Integer; тель на него. Если в процессе создания произошла
ошибка, то функция вернет –1. Для закрытия файла
обязателен вызов метода FileClose().
function
FileOpen(const Открывает файл FileName и возвращает указатель на
FileName:
string;
Mode: него. Для закрытия файла обязателен вызов метода
LongWord): Integer;
FileClose(). Файл открывается в режиме, определенном в параметре Mode (см. табл. 4.3).
function
FileSeek(Handle, Позиционирует файл с дескриптором Handle в положеOffset, Origin: Integer): ние Offset. Порядок смещения определяется параметром Origin: 0 – Offset байт от начала файла; 1 – Offset
Integer;
байт от текущей позиции; 2 – Offset байт от конца
файла.
function
FileRead(Handle: Читает Count байт в буфер Buffer из файла с дескриптоInteger; var Buffer; Count: ром Handle. Используется совместно с FileOpen() или
FileCreate().
Integer): Integer;
function
FileWrite(Handle: Записывает Count байт из буфера Buffer в файл с деInteger;
const
Buffer; скриптором Handle. Дескриптор получают от функций
FileOpen() или FileCreate().
Count: Integer): Integer;
function
FileGetDate(Han- Возвращает дату/время создания файла в формате опеdle: Integer): Integer;
рационной системы.
function
FileSetDate(Han- Устанавливает время создания файла Age с дескриптоdle: Integer; Age: Integer): ром Handle. Время должно передаваться в формате операционной системы.
Integer;
procedure
Integer);

FileClose(Handle: Закрывает файл с дескриптором Handle. Используется
совместно с функциями FileOpen() или FileCreate().

Перечисленные методы представляют собой надстройки над функциями
Win32 API. По своему функциональному назначению они являются аналогами рассмотренных ранее функций и предназначены для работы с двоичными файлами.

90

Глава 4. Основы работы с файлами

Таблица 4.3. Режимы открытия файла
Режим

Значение

Описание

fmOpenRead

$0000;

Открыть только для чтения

fmOpenWrite

$0001;

Открыть только для записи

fmOpenReadWrite

$0002;

Открыть для чтения и записи

fmShareCompat

$0000;

Совместимость со старой моделью доступа к файлам

fmShareExclusive

$0010;

Запретить другим читать файл и записывать в него

fmShareDenyWrite

$0020;

Запретить другим запись в файл

fmShareDenyRead

$0030;

Запретить другим чтение файла

fmShareDenyNone

$0040;

Разрешить другим полный доступ

Асинхронные операции ввода-вывода
Весьма часто, в особенности при работе с файлами, размещенными на удаленных компьютерах, при работе с поименованными каналами и сокетами
программисты вынуждены осуществлять операции ввода-вывода в так называемом асинхронном режиме. Любая операция записи или чтения больших объемов данных, да еще по каналу с низкой пропускной способностью,
может занимать весьма внушительный интервал времени. При проведении
синхронной операции, например вывода данных, выполнение программы
было бы приостановлено до тех пор, пока метод записи не завершит свою работу и не возвратит управление вызвавшей его программе. Это не очень эффективно, т. к. в это время могли бы выполняться какие-то другие действия, не связанные с вводом-выводом. Ключевая особенность асинхронной
операции состоит в том, что, отправив порцию данных в файл, программа не
дожидается завершения этой операции. Асинхронный метод моментально
возвращает управление вызвавшей его программе и только затем приступает к выполнению поставленной задачи.
Для организации асинхронных операций ввода-вывода целесообразно использовать методы Win32 API ReadFileEx() и WriteFileEx().
Function ReadFileEx (hFile : THandle; Buf : pointer; NumberOfBytesToRead
: cardinal; const Overlapped : pOverlapped; CompletionRoutine : pointer)
: Boolean;
Function WriteFileEx (hFile : THandle; Buf : pointer; NumberOfBytesToWrite : cardinal; const Overlapped : pOverlapped; CompletionRoutine :
pointer) : Boolean;

Здесь hFile – дескриптор файла, полученный при помощи методов Win32
API OpenFile() и CreateFile() или соответствующих методов Object Pascal
FileOpen() и CreateFile(). Buf – указатель на буфер, в который будут помещаться прочитанные данные или, наоборот, данные из которого будут записываться в файл. Третий параметр возвращает общее количество прочитанных (NumberOfBytesToRead) или записанных (NumberOfBytesToWrite) байт. Особый интерес вызывает четвертый аргумент – указатель на специальную

Управление файлами, дисками и каталогами

91

структуру OVERLAPPED, содержащую данные, которые будут использоваться
при асинхронном чтении (или записи).
type
POverlapped = ^TOverlapped;
_OVERLAPPED = record
Internal: DWORD;
InternalHigh: DWORD;
Offset: DWORD;
OffsetHigh: DWORD;
hEvent: THandle;
end;

Поля Internal и InternalHigh зарезервированы за операционной системой.
В частности, они используются ОС для определения статуса проводимой
асинхронной операции. Поля Offset и OffsetHigh соответственно хранят 32
младших и 32 старших бита позиции файла, с которой должно начинаться
чтение или запись. Поле hEvent содержит дескриптор специального объекта
синхронизации, называемого событием. Его мы изучим в главе 20 «Процессы и потоки в среде Windows». В данном контексте объект-событие применяется для передачи информации процедуре завершения. Последний параметр методов ReadFileEx() и WriteFileEx() – указатель на функцию обратного
вызова FileIOCompletionRoutine(). Эта функция вызывается при завершении
или отмене операции ввода-вывода.

Управление файлами, дисками и каталогами
Возможности языка Pascal по работе с файлами не ограничиваются сервисом чтения и записи. Существенный вклад в спектр процедур и функций,
помогающих в работе с файловой системой, вносят модули SysUtils, FileCtrl,
ExtDlgs и, конечно, модуль Windows, который позволяет использовать функции из набора Windows API.

Проверка наличия файла и каталога
Для того чтобы сразу отсечь достаточно большой объем ошибок, перед обращением к файлу или каталогу всегда проверяйте факт его существования.
function FileExists (const FileName: string): Boolean;
function DirectoryExists (const Directory: string): Boolean;

Функции возвращают true, если файл (каталог) действительно существует.

Удаление, копирование и перемещение файлов
Нет ничего проще, чем просто удалить файл. Для этого достаточно знать имя
стираемого файла и название соответствующего метода:
function DeleteFile (const FileName: string): Boolean;

Если по какой-то причине удалить файл невозможно, то функция возвратит
false.

92

Глава 4. Основы работы с файлами

В составе стандартных функций Pascal отсутствуют методы, осуществляющие копирование и перемещение файлов. Для решения задач такого рода
достаточно воспользоваться функциями из набора Windows API. Для копирования файлов используйте функцию Windows API CopyFile():
Function CopyFile (ExistingFileName, NewFileName : PChar; FailIfExists :
Boolean): Boolean;

Параметры метода: ExistingFileName – указатель на строку с полным именем
копируемого файла; NewFileName – указатель на строку с полным именем нового файла; параметр FailIfExists определяет поведение метода при совпадении имен – если параметр равен true и файл с таким именем уже существует,
то копирование прерывается. В случае успеха функция CopyFile() возвращает ненулевое значение.
var pExName, pNewName : PChar;
begin
pExName:=PChar('C:\1.txt');
pNewName:=PChar('С:\Copy_Of_1.txt');
CopyFile(pExName,pNewName,true);
end;

Для перемещения файлов используйте функцию MoveFile():
Function MoveFile(ExistingFileName, NewFileName : PChar) : Boolean;

Параметры функции MoveFile() идентичны первым двум аргументам метода
CopyFile(). В случае успеха функция возвращает ненулевое значение.

Имя файла и путь к нему
Широкий спектр методов нацелен на организацию работы с именем файла,
его расширением и путем к файлу. Почти все рассматриваемые методы объявлены в модуле SysUtils.
Таблица 4.4. Функции для работы с именем файла
Функция

Описание

function ChangeFileExt(const FileNa- Изменяет расширение в имени файла FileName
me, Extension: string): string;
на новое, определенное параметром Extension.
Возвращает новое значение имени.
function
ExcludeTrailingBack- Удаляет последнюю наклонную черту (слэш)
slash(const S: string): string;
в пути S.
function
IncludeTrailingBack- Завершает путь S наклонной чертой.
slash(const S: string): string;
function ExpandFileName(const File- Преобразует имя файла FileName в полный путь
Name: string): string;
к файлу, включая имена файла и диска.
function
ExpandUNCFileName(const Преобразует имя файла в полный путь к сетевоFileName: string): string;
му файлу: \\<Имя_сервера>\<Ресурс>

Управление файлами, дисками и каталогами

Функция

93

Описание

function ExtractFileDir(const File- Извлекает из строки с полным именем файла
Name: string): string;
путь к каталогу файла и имя каталога.
function ExtractFilePath(const File- Извлекает из строки с полным именем файла
Name: string): string;
путь к файлу, заканчивающийся слэшем.
function
ExtractFileDrive(const Извлекает из строки с полным именем файла
FileName: string): string;
имя диска, завершающееся двоеточием.
function ExtractFileExt(const File- Возвращает расширение в имени файла. В реName: string): string;
зультирующую строку входит разделительная
точка и непосредственно расширение.
function ExtractFileName(const File- Извлекает из строки с полным именем файла
Name: string): string;
имя и расширение файла.
function ExtractRelativePath(const Возвращает относительный путь к файлу.
BaseName, DestName: string): string; Здесь DestName – полный путь, BaseName – путь,
вычитаемый из полного пути.
FileName:='C:\Windows\System\comctrl32.dll';
Result:=ExtractRelativePath('c:\windows\',FileName);
В результате будет получена строка System\comctrl32.dll
function ExtractShortPathName(const Конвертирует полный путь к файлу в укороченFileName: string): string;
ный формат 8.3.
function IsPathDelimiter(const S: Проверяет наличие в позиции Index символа
string; Index: Integer): Boolean;
наклонной черты влево.
function MatchesMask(const Filename, Проверяет соответствие имени файла шаблону
Mask: string): Boolean;
маски. Функция определена в модуле Masks.
procedure ProcessPath(const EditText: Разделяет полный путь к файлу на составные
string; var Drive: Char; var DirPart: части: имя диска, путь к каталогу, имя файла.
Функция определена в модуле FileCtrl.
string; var FilePart: string);
function MinimizeName(const File- Функция определена в модуле FileCtrl. Метод
name: TFileName; Canvas: TCanvas; применяется совместно с элементами управлеMaxLen: Integer): TFileName;
ния, обладающими канвой Canvas (поверхностью
для рисования). Задача метода – поместить имя
файла FileName в области, ограниченной по ширине MaxLen пикселами. В соответствии с ограничениями функция минимизирует имя файла.

Дата и время создания файла
Самый простой способ знакомства с возрастом файла заключается в использовании функции:
function FileAge(const FileName: string): Integer;

Как видите, для этого достаточно передать путь и имя файла в параметр
FileName. Еще один метод, выполняющий аналогичную задачу:

94

Глава 4. Основы работы с файлами
function FileGetDate (Handle: Integer): Integer;

Однако здесь вместо имени требуется указатель на файл. Другими словами,
последний должен быть открыт методом FileOpen() или создан методом
FileCreate(). Оба метода возвращают дату не в привычном для нас формате
TDateTime, а в виде структуры даты-времени, принятой в Windows. Поэтому
для приведения результата к понятному для языка Pascal виду применяется метод:
function FileDateToDateTime(FileDate: Integer): TDateTime;

Существует функция, решающая и обратную задачу:
function DateTimeToFileDate(DateTime: TDateTime): Integer;
var

FileName : string;
Age : INTEGER;

begin
FileName:='C:\Autoexec.bat';
Age:=FileAge(FileName);
WriteLn(Age);
WriteLn(DateTimeToStr(FileDateToDateTime(Age)));
ReadLn;
end.

Для того чтобы назначить файлу новое время и дату, понадобится метод:
function FileSetDate(Handle: Integer; Age: Integer): Integer;
var

FileName : string;
F,Age : INTEGER;

. . .
Age:=DateTimeToFileDate(Now);
F:=FileOpen(FileName, fmOpenReadWrite);
FileSetDate(F, Age);
FileClose(F);

Атрибуты файла
В языке Pascal объявлено два метода для работы с атрибутами файла. За чтение и установку атрибутов отвечают соответственно методы:
function FileGetAttr(const FileName: string): Integer;
function FileSetAttr(const FileName: string; Attr: Integer): Integer;

В обеих функциях FileName – имя файла. Во втором методе назначение атрибутов выполняется с помощью параметра Attr. Существующие типы атрибутов представлены в табл. 4.5.
Таблица 4.5. Атрибуты файла
Атрибут

Значение

Описание

faReadOnly

$00000001

Только для чтения

faHidden

$00000002

Скрытый

95

Управление файлами, дисками и каталогами

Атрибут

Значение

Описание

faSysFile

$00000004

Системный

faVolumeID

$00000008

Диск

faDirectory

$00000010

Каталог

faArchive

$00000020

Архив

faAnyFile

$0000003F

Любой

Контроль атрибутов файла очень полезен при проведении опасных операций
с файлами. Приведем листинг программы, демонстрирующей простейший
способ защиты системных и скрытых файлов от удаления.
function DeleteMyFile(FileName : string) : Boolean;
var Attr : INTEGER;
begin
Attr:=FileGetAttr(FileName);
if (Attr and faHidden = 0) or (Attr and faSysFile = 0) then
begin
DeleteFile(FileName);
Result:=True;
end else Result:=False;
ReadLn;
end.

Наиболее частая операция, связанная с установкой (удалением) атрибута
«только для чтения», унифицирована и представлена в лице метода:
function FileSetReadOnly(const FileName: string; ReadOnly: Boolean): Boolean;

Здесь FileName – имя файла, ReadOnly – состояние атрибута.

Размер файла
Зачастую применение входящего в арсенал Delphi метода FileSize() недостаточно удобно, т. к. его единицей измерения является не байт, а запись. Для
выяснения размера файла в байтах можно использовать метод из состава
Windows API:
Function GetFileSizeEx(hFile : HANDLE; pFileSize : Pointer) : Boolean;

Функция снабжена двумя аргументами: hFile – дескриптор файла и hFileSize – указатель на переменную типа INT64, в которую будет записан полученный размер.
var

FileName : string;
F : Integer;
Size : INT64;

begin
FileName:='C:\autoexec.bat';
F:=FileOpen(FileName,fmOpenRead);
Size:=GetFileSize(F, @Size);

96

Глава 4. Основы работы с файлами
WriteLn(Size);
FileClose(F);
ReadLn;
end.

Организация поиска файлов и каталогов
В языке Pascal реализованы три специализированные функции, осуществляющие поиск файлов и каталогов в соответствии с критериями, определяемыми программистом. Все три функции должны работать в тесном союзе
друг с другом и вызываться только в определенной последовательности.
Первая из функций предназначена для инициализации поиска и нахождения первого файла, соответствующего нашим требованиям:
function FindFirst(const Path: string; Attr: Integer;var F: TSearchRec): Integer;

В первом аргументе метода необходимо указать каталог, где будет осуществляться поиск, и маску поиска. Например, маска ‘C:\Mydir\*.txt’ задает поиск всех файлов с расширением txt в каталоге ‘C:\Mydir’. Второй аргумент
определяет атрибуты искомых файлов (см. табл. 4.5). Один файл может обладать одним и более атрибутами.
Если файл успешно найден, функция вернет нулевое значение, а данные
о найденном файле окажутся в переменной F типа TSearchRec.
type
TSearchRec = recordTime:
Integer;
Integer;
Attr: Integer;
Name: TFileName;
ExcludeAttr: Integer;
FindHandle: THandle;
FindData: TWin32FindData;
end;

//дата файла
//размер в байтах
//атрибуты файла
//имя файла
//не документировано
//дескриптор файла
//структура Win API с дополнительной информацией

Вторая функция FindNext() является логическим продолжением метода
FindFirst(). Задача метода – поиск оставшихся файлов. При нахождении
файла функция возвращает нулевое значение и записывает данные в переменную F.
function FindNext(var F: TSearchRec): Integer;

По завершении поиска метод FindClose() обязан освободить использованные
ресурсы:
procedure FindClose(var F: TSearchRec);

Теперь обратимся к примеру, демонстрирующему способ сбора данных обо
всех файлах и каталогах, размещенных в корне диска C:\. Поиск данных
осуществляется в цикле до тех пор, пока метод FindNext() не вернет отличное
от нуля значение.
var

F : TSearchRec;

Управление файлами, дисками и каталогами

97

begin
if FindFirst('c:\*.*',faAnyFile,F)=0 then
Repeat
if (F.Attr and faDirectory) <> 0 then //если это каталог, то отобразим только имя
WriteLn(F.Name)
else
//это файл, выводим более подробную информацию
WriteLn(F.Name,#9,F.Size,#9,DateTimeToStr(FileDateToDateTime(F.Time)));
until FindNext(F)<>0;
FindClose(f);
ReadLn;
end.

При организации поиска стоит обратить внимание на некоторые особенности проверки принадлежности определенного атрибута набору атрибутов
найденного файла. Для этого мы выясняем результат комбинации атрибутов
файла и нашего атрибута при помощи оператора and: (F.Attr and faDirectory)
<>0. Если возвращено ненулевое значение, то атрибут входит в набор.
Говоря о поиске, стоит упомянуть еще один метод, проверяющий перечень
путей к искомому файлу и выбирающий верный:
function FileSearch (const Name, DirList: string): string;

Здесь в параметре Name указывается имя искомого файла, а в параметре DirList
передается список путей, разделенных точкой с запятой. Если в перечне путей не окажется правильного, метод вернет пустую строку:
FilePath:=FileSearch('comctl32.dll','C:\;C:\Windows;C:\Windows\System');

Управление каталогами
Набор методов, предназначенных для работы с каталогами, не столь впечатляющий по сравнению с набором процедур и функций для работы с файлами. Однако и этот золотой минимум позволяет решать все возникающие перед программистом задачи.
Таблица 4.6. Функции для работы с каталогами
Функция
procedure GetDir(Drive:
var S: string);

Описание
Byte; Возвращает текущий каталог на диске Drive. Номер
диска выбирается аналогично функции DiskFree().

function GetCurrentDir: string; Возвращает текущий каталог.
function
SetCurrentDir(const Устанавливает каталог Dir текущим. В случае успеDir: string): Boolean;
ха возвращает true.
procedure ChDir(S: string);
function CreateDir(const
string): Boolean;

Выбирает новый текущий каталог.
Dir: Создает новый каталог и возвращает true в случае
успеха.

function DirectoryExists(Name: Проверяет наличие каталога Name и возвращает true
string): Boolean;
в случае успеха.

98

Глава 4. Основы работы с файлами

Таблица 4.6 (продолжение)
Функция

Описание

function RemoveDir(const
string): Boolean;

Dir: Удаляет пустой каталог Dir. В случае успеха возвращает true.

function ForceDirectories(Dir: Создает все каталоги, определенные в параметре
string): Boolean;
Dir. В случае успеха возвращает true.

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

GetWindowsDirectory(Buffer : PChar; Size : Integer):Integer;
GetSystemDirectory (Buffer : PChar; Size : Integer):Integer;
GetTempPath(Size : Integer; Buffer : PChar):Integer;
GetCurrentDirectory(Size : Integer; Buffer : PChar):Integer;

Функции возвращают пути соответственно к каталогу Windows, системному
каталогу, каталогу временных файлов и текущему каталогу.
var Buf : array [0..MAX_PATH-1] of Char;
begin
GetWindowsDirectory(Buf,SizeOf(Buf));
WriteLn(Buf); ReadLn;
end;

Если методы выполнились без ошибок, то они возвратят количество символов в пути, в противном случае вы получите нулевое значение.
В главе 29, посвященной пространству имен оболочки Windows, мы познакомимся
с еще одной многоцелевой функцией SHGetSpecialFolderPath(), предназначенной
для возврата путей к специальным папкам.
Напоминаю, что для преобразования строки из формата, привычного для Windows,
в формат AnsiString следует использовать метод StrPas():
S:=StrPas(Buf);

Для переименования или перемещения каталога используйте метод MoveFile(), рассмотренный ранее, или изучите еще более «продвинутые» версии –
MoveFileEx() и MoveFileWithProgress().

Работа с дисками компьютера
Говоря о дисках, первое, что нас может заинтересовать, – сколько на нем осталось свободного места. Для этого в языке Pascal реализована функция:
function DiskFree(Drive: Byte) : Int64;

Функция возвращает количество свободного места на диске в байтах. Параметр Drive требует передачи номера диска. Например, 0 = текущий, 1 = A,
2 = B, 3 = С и т. д. Если метод не в состоянии выяснить размер свободного
места на диске, он возвращает –1.

Управление файлами, дисками и каталогами

99

Общий размер диска сообщает метод DiskSize(). Выбор номера диска аналогичен методу DiskFree().
function DiskSize(Drive: Byte) : Int64;

При работе с дисками неоценимую поддержку программисту оказывает
Windows API. Кстати, оба рассмотренных выше метода в тайне от нас пользуются услугами функции Windows API GetDiskFreeSpaceEx(). Преимущество
этого метода в том, что он всего за один вызов делает то, с чем DiskFree()
и DiskSize() справляются только вдвоем. А именно он возвращает количество свободных байт (FreeBytesAvailable), доступных в данный момент пользователю, и общее количество байт на диске (TotalNumberOfBytes). При этом имя
диска передается в привычном виде, например C:\, а не в виде цифры, как
в случае функций языка Pascal.
Function GetDiskFreeSpaceEx(Drive : PChar; FreeBytesAvailable, TotalNumberOfBytes, TotalNumberOfFreeBytes : INT64):Boolean;

Если метод выполнился удачно, он возвратит true. Четвертый параметр функции не используется, и вместо него передавайте указатель в никуда – nil.
Еще один весьма полезный метод из обоймы операционной системы Windows расскажет нам о типе диска, подключенного к системе:
Function GetDriveType (Drive : PChar) : Integer;

Метод требует указания всего одного параметра – указателя на строку с именем диска. Результат возвращается в соответствии с табл. 4.7.
Таблица 4.7. Константы типа диска
Значение

Описание

0

Невозможно определить тип диска.

1

Корневой каталог отсутствует.

DRIVE_REMOVABLE

Съемный диск.

DRIVE_FIXED

Жесткий диск.

DRIVE_REMOTE

Удаленный (сетевой) диск.

DRIVE_CDROM

Привод CD.

DRIVE_RAMDISK

Диск в оперативной памяти.

Для закрепления пройденного материала реализуем скромный пример, посвященный изучению состава дисков компьютера.
program ...;
{$APPTYPE CONSOLE}
uses SysUtils, Windows;
const ByteInMb=1048576; // 220, именно столько байт в 1 Мбайт
var Ch : CHAR;
iDriveType : Integer;
sDriveType, sSize, sFree : string;

100

Глава 4. Основы работы с файлами

AllBytes, FreeBytes : INT64;
begin
for Ch:='A' to 'Z' do
begin
iDriveType:=GetDriveType(PChar(Ch + ':\'));
if iDriveType<=1 then Continue;
//если диск не обнаружен, пропускаем шаг
case iDriveType of
DRIVE_REMOVABLE : sDriveType:='Removable';
DRIVE_FIXED
: sDriveType:='Fixed';
DRIVE_REMOTE
: sDriveType:='Network';
DRIVE_CDROM
: sDriveType:='CD-ROM';
DRIVE_RAMDISK : sDriveType:='RAM'
else
sDriveType:='Unknown'
end;
sDriveType:='['+sDriveType+']';
if GetDiskFreeSpaceEx(PChar(Ch + ':\'),FreeBytes,AllBytes,nil)=True then
begin
sSize:=Format('Size: %d.%d Mb',[AllBytes div ByteInMb,
AllBytes mod ByteInMb]);
sFree:=Format('Free: %d.%d Mb',[FreeBytes div ByteInMb,
FreeBytes mod ByteInMb]);
end
else sSize:=''; sFree:='';
WriteLn(Ch+':\',
sDriveType,
#9, sSize, #9,
sFree);
end;
ReadLn;
end.

Задача рассматриваемой программы – собрать сведения обо всех дисках, установленных на компьютере, выяснить их тип, размер и наличие свободного
пространства. Сведения о размерах выводятся не в байтах, а в мегабайтах;
для этого осуществляется деление на константу ByteInMb, содержащую количество байт в одном мегабайте (220=1048576).
Сведения о диске вашего компьютера пополнит еще одна функция из пакета
Windows API:
Function GetVolumeInformation(
Drive : PChar;
// указатель на строку с именем диска
VolumeNameBuffer : PChar;
// буфер, в который будет записано имя тома
VolumeNameSize : DWord;
// размер буфера с именем тома
VolumeSerialNumber : DWord;
// указатель на серийный номер тома
MaximumComponentLength : DWord; // максимальное число символов
// в имени файла
FileSystemFlags : DWord;
// указатель на флаги файловой системы
FileSystemNameBuffer : PChar;
// буфер с названием файловой системы
FileSystemNameSize : DWord
// размер буфера с названием
// файловой системы
) : Integer;

101

Резюме

У начинающего программиста обилие параметров метода может вызвать некоторое содрогание, однако особых причин для волнения нет. В первом аргументе требуется указать имя диска (не забыв про двоеточие и слэш), о котором нам хочется получить данные. Во все остальные параметры требуется
подставить соответствующие массивы и переменные, в которые функция
с радостью вернет результаты анализа диска. Если все сложится успешно,
метод вернет любое ненулевое значение.
uses SysUtils, Windows;
var VolumeNameBuffer, FileSystemNameBuffer: array [0..MAX_PATH-1] of Char;
SerNum, MaxLen, NotUsed, VolFlags : DWORD;
begin
GetVolumeInformation(PChar('C:\'),VolumeNameBuffer,
DWord(Sizeof(VolumeNameBuffer)),
@SerNum,
MaxLen,
VolFlags,
FileSystemNameBuffer,
DWord(SizeOf(FileSystemNameBuffer)));
WriteLn('Drive C:\ information');
WriteLn('Volume label: ', VolumeNameBuffer);
WriteLn('File system: ', FileSystemNameBuffer);
WriteLn('Serial number: ', SerNum);
WriteLn('Maximum length: ', MaxLen);
ReadLn;
end.

В примере осталось нераскрытым содержание параметра FileSystemFlags.
Это флаг или комбинация флагов, выявляющая некоторые особенности файловой системы, например такие, как поддержка регистра в названиях файлов, возможность сжатия тома и т. п. Более полную информацию можно получить из справочной системы.
В заключение познакомимся еще с одной функцией Windows API, которая
предназначена для смены метки тома:
Function SetVolumeLabel(Drive, VolumeName : PChar):Boolean;

Название тома передается через параметр VolumeName.

Резюме
Порядок работы с файлом, а также перечень и особенности вызова методов
определяются типом файла. Различают три типа файлов: текстовый, типизированный и двоичный (нетипизированный). Действия, связанные с чтением
и записью данных, называются операциями ввода-вывода. Помимо операций ввода-вывода среда Delphi и операционная система Windows предоставляют методы для управления файлами и каталогами. Например, процедуры
и функции, предназначенные для поиска файла, получения информации
о файле, для удаления, копирования и перемещения файла.

5
Введение в объектно-ориентированное
программирование
Язык программирования Object Pascal и его достойный преемник, среда
программирования Delphi, построены на основе получившей широкое развитие на стыке 70–80-х годов XX века теории объектно-ориентированного
программирования (Object-Oriented Programming, OOP). В то время идея
описания программ в базисе логических сущностей и взаимодействия между ними не была такой уж бесспорной, а у некоторых оппонентов даже вызывала определенное недоумение.
Пока консервативно настроенные личности занимались критикой новой
идеи и всячески пропагандировали традиционный по тем временам процедурный стиль, компания Borland и корпорация Microsoft активно занялись
разработкой концептуально новых систем программирования. Спустя сравнительно небольшой промежуток времени на рынке появились среды разработки ПО, совершившие революционный переворот в самой идее программирования. В первую очередь это объектно-ориентированные C++ и Object
Pascal, а чуть позднее Microsoft Visual C++, Borland C++ Builder и, конечно
же, Borland Delphi.
Появление даже первых версий этих систем поумерило пыл критиков ООП.
Трудно придираться к программным продуктам, в которых создание классического примера «Hello, World» занимает не более минуты и обходится
без единой строки кода. Для сравнения аналогичная задача на старом добром языке С потребует не менее 50 строк кода.
Подчеркну, что материал этой главы не претендует на «всеобъемлющее изложение концепции объектно-ориентированного программирования». Задача скорее обратная – познакомить читателя с основными понятиями ООП
и провести вступительную экскурсию по объектам и классам, дабы подготовить новичков к изучению среды программирования Delphi. Более углубленная версия предлагается в главе 16 «Создание компонентов».

Объект и класс

103

Объект и класс
Уже само название концепции «объектно-ориентированное программирование» указывает на то, что ключевой фигурой в OOП является объект. Что
же это такое? В окружающей нас среде объект – это то, что можно пощупать
руками, это книга, кнопка на клавиатуре, сама клавиатура, настольная
лампа, проезжающий за окном автомобиль. Каждый объект обладает некоторыми характеристиками, сравнивая которые мы можем судить о сходстве
или различии объектов. Так, несмотря на схожесть кнопок клавиатуры, мы
различаем их по сопоставленному им символу алфавита. Кроме того, есть
характеристики, взглянув на которые мы можем судить о текущем состоянии объекта, например, лампа включена или выключена, автомобиль мчится со скоростью 60 км/ч.
Все присущие объекту характеристики в терминах OOП называют полями.
По сути, это принадлежащие объекту переменные определенного типа, в которые записываются значения, отражающие состояние объекта. Для управления объектом предназначены его методы. Настольной лампой управляют
два ключевых метода: включить и выключить. У автомобиля этих методов
значительно больше: он заводится, набирает скорость, изменяет направление движения, замедляет движение и все это время пожирает бензин.
В первую очередь методы воздействуют на поля объекта. Так, практически все
методы автомобиля сосредоточены вокруг поля, описывающего его скорость.
Если методы объекта возвращают какое-нибудь значение, то они реализуются
в виде функции, в противном случае метод представляется процедурой.
Объект не может возникнуть из воздуха, среда программирования каким-то
образом должна быть проинформирована о его характеристиках. Поэтому
предварительно программист описывает объект; такое описание называется
классом. Класс – это чертеж будущего объекта, в котором учитываются не
только его конструктивные элементы (поля), но и определяются способы
управления этими элементами – методы класса.
Определение класса начинается с ключевого слова type, за которым следуют
имя класса, его поля и методы. Завершается описание директивой end. Например, объявление класса простейшего автомобиля могло бы выглядеть
примерно так:
type
TAutomobile = class
fSpeed : smallint;
procedure SetSpeed(Value : SmallInt);
end;

Созданный на основе класса объект способен только изменять скорость –
содержимое поля fSpeed.
В объявление класса могут входить другие классы; таким образом можно
создавать сложные составные объекты. Допустим, что мы хотим снабдить
автомобиль двигателем. Для этого объявим новый класс, описывающий этот
двигатель:

104

Глава 5. Введение в объектно-ориентированное программирование

type
TEngine = class
fOn : Boolean;
procedure EngineOn;
procedure EngineOff;
end;

//класс двигатель
//поле вкл/выкл
//включить двигатель
//выключить двигатель

Двигатель TEngine умеет запускаться и останавливаться, для чего реализованы соответствующие методы. О текущем состоянии двигателя можно судить
по полю fOn.
type
TAutomobile = class
fSpeed : smallint;
fEngine : TEngine;
//интеграция класса TEngine в состав класса TAutomobile
procedure SetSpeed(Value : SmallInt);
end;

Двигатель интегрируется в состав общего класса автомобиля в виде отдельного поля. Для обращения к полю или методам вложенного класса необходимо лишь указать его принадлежность. Например, для того чтобы завести
автомобиль, потребуется следующая строка кода:
var Automobile : TAutomobile;

Automobile.fEngine.EngineOn;

Скажете, это все теория. Хорошо, взглянем на классы и объекты с практической стороны. При разработке обычного приложения с объявлением классов
и созданием объектов Delphi справляется без посторонней помощи. Чтобы
убедиться в этом, запустите среду программирования. По умолчанию создается новый проект с одной-единственной формой. Заглянув в редактор кода,
вы увидите следующие строки:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs;
type
TForm1 = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
var Form1: TForm1;
implementation
{$R *.dfm}
end.

Пока наш проект практически пуст. В нем объявлен единственный класс
формы TForm1, а в разделе переменных объявлена переменная объектного ти-

Объект и класс

105

па Form1 : TForm1. После старта приложения
и создания экземпляра класса TForm1 в этой
переменной будет храниться ссылка на
объект. А теперь разместите на поверхности формы любой из компонентов, например строку ввода (элемент управления TEdit со страницы Standard, рис. 5.1). После
этого вернитесь в редактор кода. В теле Рис. 5.1. Форма со строкой ввода
объявления класса TForm1 появилось одно
очень важное изменение – новый объект Edit1, создаваемый из класса TEdit.
type
TForm1 = class(TForm)
Edit1: TEdit;
private
{ Private declarations }
public
{ Public declarations }
end;

Отметим одну важную особенность, касающуюся обращения к объекту. Если нам потребуется обратиться к строке ввода Edit1 из этого же модуля Unit1,
то достаточно просто указать имя вызываемого компонента:
Edit1.Text:= 'Hello World!';

Однако приложения очень часто состоят из двух и более форм. И если вы попытаетесь из другой формы изменить текст в строке ввода формы Form1 предложенным выше способом, то компилятор сообщит, что он не знает о существовании компонента с именем Edit1.
При обращении к компоненту, принадлежащему другой форме, сначала надо сослаться на форму, владеющую этим компонентом:
Form1.Edit1.Text:= 'Hello World!';

Это правило также относится к вызову всех опубликованных (описанных в секции
public) полей и методов другой формы.

Создание и разрушение объекта
Все размещаемые на поверхности формы компоненты создаются и уничтожаются автоматически. В простейших приложениях программисту, как
правило, даже нет необходимости вмешиваться в их жизненный цикл. Но
если вы собираетесь создавать профессиональные программные продукты,
то безусловно стоит узнать, как рождается и умирает объект.
Любой класс снабжен двумя специализированными методами: конструктором (constructor) и деструктором (destructor). Конструктор предназначен для
создания экземпляра класса; в результате его вызова рождается новый объект и инициализируются его поля. Обычно конструктор реализуется в виде
функции Create(). Деструктор – это антипод конструктора; его задача кар-

106

Глава 5. Введение в объектно-ориентированное программирование

динально противоположная – уничтожить объект и освободить отведенные
под него системные ресурсы. По существующей традиции деструктор реализуется в виде процедуры Destroy().
Описание класса, включающее конструктор и деструктор, выглядит примерно так:
type
TAutomobile = class

constructor Create;
destructor Destroy;
end;

Пусть вас не вводит в недоумение несколько нетрадиционный синтаксис
объявления этих методов. В данном случае нет необходимости применять
привычные ключевые слова procedure и function, а также не надо указывать
тип возвращаемого функцией Create() значения, т. к. компилятор и без нашей помощи знает, что конструктор создает экземпляр класса TAutomobile.
На практике применение конструктора и деструктора не вызывает особых
затруднений:
var Automobile : TAutomobile;
begin
Automobile:= TAutomobile.Create;
//операции с объектом Automobile
Automobile.Destroy;
end;

Программист объявляет переменную объектного типа, конструирует этот
объект, проводит с ним запланированные операции и затем уничтожает его.
Зачастую обязанности по созданию и уничтожению объектов берет на себя
Delphi. Например, при разработке приложения средней сложности среда
программирования в состоянии взять на себя полную ответственность за создание формы и принадлежащих ей компонентов. Но если программист намерен создавать объекты самостоятельно, то он должен помнить правило:
все созданное нашими руками по окончании работы должно быть уничтожено нами же. В этом случае не принято полагаться на сообразительность любой среды разработки, в том числе и Delphi.
Функция Create() относится к группе так называемых методов класса (class functions). Это особая категория методов, которые могут вызываться не от имени
объекта, а от имени его класса.

Операторы класса
В Delphi предусмотрено два специализированных оператора, называемых
операторами класса: is и as. Оператор is предназначен для проверки принадлежности объекта к определенному классу:
if (MyObject is TComponent)=True then …

Инкапсуляция

107

Ключевая особенность оператора is в том, что он проверяет не только непосредственного предка объекта, а всю иерархическую цепочку наследования
объекта в целом. И если TComponent будет предком объекта MyObject даже в десятом колене, то конструкция вернет true.
Оператор as называется оператором приведения типа. Это очень грозное
и притом опасное оружие, поэтому использование оператора as должно быть
максимально продуманным.
with (MyObject as TControl) do …

Оператор приведения типа позволяет явным образом указывать Delphi, что
объект MyObject должен действовать как экземпляр класса TControl. И горе
той программе, которая с помощью оператора as попробует заставить объект
выполнить несвойственные ему действия. Такая попытка в лучшем случае
закончится вызовом исключительной ситуации. Поэтому сразу приведу
пример безопасного приведения типов:
var i : integer;
begin
for i:=0 to Form1.ComponentCount-1 do
if Form1.Components[i] is TCustomEdit then
with Form1.Components[i] as TCustomEdit do Text:=#0;
end;

Предложенный код проверяет все расположенные на форме компоненты на
принадлежность классу TCustomEdit и очищает свойство Text.

Методы класса
Метод класса – это особая категория процедур или функций, способных выполняться без создания экземпляра класса. Классический пример метода
класса – уже знакомый нам конструктор. Объявление метода класса обычно
начинается с ключевого слова class:
type TMyObject = class

class procedure MyObjectProcedure;
end;

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

108

Глава 5. Введение в объектно-ориентированное программирование

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

Области видимости
При описании класса программист имеет право определять степень доступности (видимости) его полей и методов. Это один из способов защиты наиболее критичных элементов класса от несанкционированного вмешательства.
Область видимости поля (метода) класса зависит от того, в какой из четырех
возможных секций оно объявлено: private, protected, public и published.
type
TMyObject = class
private
... { секция частных объявлений }
protected
... { секция защищенных объявлений }
public
... { секция общих объявлений }
published
... { секция опубликованных объявлений }
end;

Поля и методы, доступ к которым нежелателен, обычно размещают в секциях private и protected. Наиболее защищена секция private. К размещенным
в ней полям и методам возможно обращение только из того же программного модуля, в котором описан этот класс. Секция protected несколько приоткрывает завесу секретности – находящаяся в ней информация доступна для
классов-потомков.
Секция public предоставляет объявленные в ней поля и методы для общего
пользования всем желающим. Секция published самая доброжелательная. Например, объявленными в ней данными умеет пользоваться Инспектор объектов. Это возможно благодаря тому, что для этой секции класса генерируется
информация о времени выполнения (Runtime Type Information, RTTI). Поэтому в секции published обычно объявляют все свойства и обработчики событий объекта.

Свойства объекта
Благодаря инкапсуляции объект обладает особыми характеристиками, называемыми свойствами (property). Начинающий программист зачастую воспринимает свойство как банальную переменную, принадлежащую тому или
иному объекту. В эту переменную он может помещать какие-то значения,
например текст заголовка кнопки, определять геометрические размеры объ-

Наследование

109

екта, изменять его цвет и т. п. Сравнение свойства с переменной нельзя назвать ошибочным (хотя бы потому, что именно этого впечатления упорно добивались разработчики Delphi), но все-таки это поверхностное суждение.
На самом деле свойство – это способ доступа к полям объекта. В нем, как
правило, инкапсулировано два метода: чтение и запись. Эти методы обеспечивают связь между полем объекта и соответствующим ему свойством; более
того, все это делается в глубокой тайне от работающего с объектом программиста. Для большей наглядности вернемся к нашему автомобилю, описанному классом TAutomobile.
type
TAutomobile = class
private
fSpeed : SmallInt;
fEngine : TEngine;

procedure SetSpeed(value : smallint);
published
property Speed : SmallInt read fSpeed write SetSpeed;

end;

В первую очередь обратите внимание на наличие внутри объявления класса
секций частных (private) и публичных (published) объявлений. Программисту, воспользовавшемуся нашим объектом, будут доступны только свойства
и методы, объявленные в секции published, и он может даже не подозревать
о существовании полей fEngine и fSpeed. Зато ему предоставлена возможность обращаться к свойству Speed, причем любые действия, связанные с попыткой изменить скорость, будут контролироваться спрятанной процедурой SetSpeed(). Устанавливая новое значение скорости (в Инспекторе объектов или непосредственно из кода приложения), начинающий программист
и не подозревает, что его действия контролируются:
procedure TAutomobile.SetSpeed(value : smallint);
begin
if fEngine.fOn=False then ShowMessage('Отключен двигатель!')
else
if (value>=0) and (value<=300) then fSpeed:=value
else ShowMessage('Недопустимая скорость!')
end;

Таким образом «умный» объект не даст изменить скорость при выключенном двигателе и плюс ко всему убедится, что новое значение входит в допустимый диапазон.

Наследование
Вторым столпом ООП считается наследование. Это совсем простое понятие,
смысл которого в том, что при описании нового класса допустимо брать за основу его родительский класс. Таким образом достигается преемственность по-

110

Глава 5. Введение в объектно-ориентированное программирование

колений: дочерний класс наследует все методы и свойства своего предка. Ограничений на длину цепочки наследования нет; в Delphi объявлены классы,
имеющие в родословной больше десятка предков. Таким образом, самый последний в иерархии наследования класс, если так можно выразиться, – самый
опытный: он вобрал все лучшее от всех своих «дедушек» и «прадедушек».
Независимо от функционального назначения объекта в роли его самого «древнего»
предка выступает основа основ иерархии библиотеки визуальных компонентов Delphi – класс TObject. Кроме того, TObject – единственный класс, не имеющий предка;
он, так сказать, создан с чистого листа.

Механизм наследования значительно упрощает жизнь разработчика компонентов. Благодаря наследованию отпадает необходимость описывать одни
и те же характеристики для разных объектов. Вместо этого достаточно найти наиболее подходящий родительский класс и на его основе описать достойного потомка.
В нашем случае класс TAutomobile может быть превращен практически в любую разновидность транспортного средства от бульдозера до трамвая. Тем
более, что золотым минимумом (двигателем и свойством, описывающим
скорость) он уже обеспечен. Допустим, мы хотим создать грузовик, Tlorry,
предназначенный для перевозки тяжестей, поэтому необходимо снабдить
класс TLorry параметром, характеризующим количество груза.
type
TLorry = class(TAutomobile)
fCargoCount : real;

property CargoCount : real read GetCargoCount write SetCargoCount;
end;

Обратите внимание на особенность объявления дочернего класса. Во фрагменте кода, посвященном созданию потомка класса TAutomobile, за ключевым словом class в круглых скобках следует имя родительского класса:
TLorry = class(TAutomobile)

С этого момента, встретив подобную строку кода в любом листинге, вы сразу
поймете, кто чей родственник. Если же вдруг за словом class будет пусто, то
это признак того, что класс создается на основе TObject. Поэтому объявление
класса:
TAutomobile = class(TObject)

эквивалентно объявлению:
TAutomobile = class

Процесс наследования допускает не только полный перенос свойств и методов от предка к потомку, при желании он вполне управляем. Мы имеем полное право спрятать ненужные свойства и методы или видоизменить их. Так
что наследование – это достаточно гибкий механизм, позволяющий развивать дочерние классы.

Полиморфизм

111

Полиморфизм
Полиморфизм едва ли не самый сильный козырь ООП. Дословная расшифровка этого термина обозначает обладание многими формами. Идея полиморфизма тесно связана с наследованием и заключается в праве экземпляра
некоторого класса представлять все классы из его иерархической цепочки.
Более того, благодаря полиморфизму мы получаем право скрывать или переопределять поведение унаследованных методов. Поэтому различные по
содержанию процедуры и функции всевозможных объектов могут использовать одно и то же имя, а вызов метода будет приводить к выполнению кода,
соответствующего конкретному экземпляру объекта.
В качестве номинантов на первую премию в области полиморфизма я бы привел абстрактные классы TComponent и TDataSet (один из прямых наследников TComponent).
Класс TComponent служит базисом для разработки компонентов – всего того, что
вы видите на палитре компонентов Delphi. Класс TDataSet представляет собой
унифицированный, не зависящий от конкретной платформы (Paradox, dBase, Access,
InterBase и т. д.) набор данных, служащий основой для построения всех компонентов доступа к базам данных.

Рассмотрим небольшой пример, демонстрирующий идеи полиморфизма.
В листинге объявлено три класса: животное (TAnimal), кошка (TCat) и лев
(TLion). Предположим, что в роли предка всех кошачьих выступает класс
TAnimal, на его основе создан класс TCat и на вершине пирамиды наследования
царствует TLion. Все три зверя снабжены одним единственным методом –
процедурой Run().
type TAnimal = class
procedure Run; virtual; abstract;
end;
type TCat = class(TAnimal)
procedure Run; override;
end;
type TLion = class(TCat)
procedure Run; override;
end;
implementation
procedure TCat.Run;
begin
ShowMessage('Cat run');
end;
procedure TLion.Run;
begin
ShowMessage('Lion run');
end;

Родоначальник всех зверей, класс TAnimal, сделан абстрактным. Это особый
тип класса, который никогда не сможет превратиться в объект; в рамках
этого класса программист лишь рисует наброски потенциального объекта.

112

Глава 5. Введение в объектно-ориентированное программирование

Основу класса составляют заголовки абстрактных (abstract) методов. Это
особый вид метода, который в рамках абстрактного класса лишь объявляется и не имеет реализации. Однако наличие абстрактного метода обязывает
класс-наследник описать этот метод, как это и сделано в TCat и TLion. Таким
образом, абстрактный класс – это своего рода договор о намерениях, обязывающий всех своих потомков действовать по заложенной в нем идее.

Программирование, управляемое событиями
Любой пользователь Windows, как говорится, «с младых лет» на интуитивном уровне представляет, что такое событие. Зачастую (щелкнув кнопкой
мыши или нажав клавишу на клавиатуре) он сам выступает инициатором
события. События такого рода называют событиями пользователя (user
events). Еще чаще на роль застрельщика события выдвигается Windows.
Операционная система вызывает системные события (system events). И последняя разновидность событий – события, сгенерированные самим элементом управления. Это внутренние события объекта (internal events). На возникновение события уважающая себя программа обязательно отреагирует.
Каким образом? Зависит от профессиональной пригодности программиста.
В понимании операционной системы событие – это ответ на сообщение Windows. Таких сообщений не одна сотня, а умение грамотно реагировать на поступление определенного сообщения требует от нас глубоких знаний операционной системы. Библиотека визуальных компонентов Delphi во многом
упрощает жизнь программиста, инкапсулируя отклики на наиболее часто
поступающие сообщения в своих классах. С точки зрения Object Pascal событие – это особый тип свойства, называемый свойством процедурного типа. Благодаря этим свойствам программист получает возможность описать
реакцию на событие. Основной набор обработчиков событий заложен в классах TControl и TWinControl.

Резюме
Концепция объектно-ориентированного программирования зиждется на трех
основных понятиях: наследовании, инкапсуляции и полиморфизме. Благодаря наследованию мы получаем право передавать родовые черты классапредка классу-потомку, т. е. обеспечивать преемственность поколений. Инкапсуляция необходима для сокрытия от пользователя особенностей реализации класса. Таким образом разработчик класса повышает его отказоустойчивость, защищая класс от некорректных внешних действий. Полиморфизм
предоставляет классу-потомку право вызывать, скрывать или при необходимости переопределять поведение унаследованных методов.
Класс – это описание объекта на алгоритмическом языке. В классе объявляются поля и методы будущего объекта. Напрямую доступ к полям объекта
запрещен; вместо этого программист работает со свойствами, которые инкапсулируют поля. Жизненный путь объекта начинается с момента вызова
его конструктора и завершается обращением к деструктору.

6
Невидимые классы
Еще в начале 80-х годов прошлого века творцы программного обеспечения
и не помышляли о новом «способе» разработки программ – «программировании мышкой». Невероятно, но факт – простейшее приложение под Windows
в Delphi реально получить уже после пары щелчков кнопкой мыши по палитре компонентов и набора минимума строк кода. Классический пример – программка, выводящая сообщение «Hello World!» по нажатию кнопки в Delphi,
потребует от нас «колоссальных» усилий по размещению на форме самой
кнопки и метки текста и описанию процедуры обработчика события OnClick():
Label1.caption:='Привет, Мир!';

Если не устали, нажмите клавишу F9 – работа завершена! Уже давным-давно
программа «Hello World!» считается хрестоматийным примером программирования и первым проектом для новичков. Еще в «старозаветные» времена весьма талантливые программисты Брайан Керниган и Деннис Ритчи
листингом «Hello, World!» открыли классический курс изучения языка С
(«The C Programming Language»). Как вы думаете, сколько строк кода надо
написать на обычном языке С, чтобы создать аналог нашей программы для
работы в Windows? Отвечу: для того чтобы разработать на С программу, создающую окно с приветствием, потребуется около 50 строк исходного кода.
На мой взгляд, титанический труд…
Работа в среде визуального программирования Delphi полностью отвечает
широко применяемому в современных программных продуктах принципу:
что видишь, то и получаешь. Вместо мучительного выдавливания из головы
строк исходного кода, описывающих элементы управления и их поведение,
процесс проектирования в Delphi сводится к размещению требуемых компонентов (разработанных высококвалифицированными инженерами в стенах
Borland) на рабочих формах проекта.
Спектр существующих компонентов библиотеки VCL не только не уступает,
но даже превосходит аналогичный по назначению набор элементов управления MFC (Microsoft® Foundation Class) своего конкурента – языка Visual С++

114

Глава 6. Невидимые классы

из пакета программирования Visual Studio, в особенности в области технологии работы с базами данных. Более того, библиотека визуальных компонентов является полностью доступной системой с открытыми текстами исходных кодов и готова к сотрудничеству с вами как с потенциальными создателями новых элементов управления.
Данная глава не случайно предшествуTObject
ет разделам, посвященным компонентам Delphi. Дело в том, что библиотека
TStream
визуальных компонентов VCL зиждется на рассматриваемых здесь классах.
На рис. 6.1 приведен фрагмент иерарTPersistent
хического дерева VCL. Особенность
представленных на рисунке классов
TComponent
в том, что они являются фундаментом
для построения полнофункциональTControl
ных визуальных элементов управления
и при этом остаются в тени. Сотрудники Borland позаботились о том, чтобы
TWinControl
начинающий программист получил
возможность создать жизнеспособное
TGraphicControl
приложение под Microsoft® Windows,
даже не подозревая о существовании
Рис. 6.1. Фрагмент иерархии
этих невидимых классов. Но согласиключевых классов VCL
тесь, что словосочетание «профессиональное приложение» звучит значительно убедительнее, чем «жизнеспособное», но профессионалом без знаний основ VCL никак не станешь.
Что такое компонент? Для разработчика приложения это некий объект, размещенный на форме проекта. Настраивая свойства компонента и описывая
события в Инспекторе объектов, мы получаем возможность изменять внешний вид и определять поведение нашего компонента. Компонент, размещенный на форме, принято называть элементом управления. Физически компонент – это некий программный код, в котором описаны особенности поведения создаваемого на его основе элемента управления.
Все компоненты в Delphi принято разделять на две категории: визуальные
и невизуальные. В свою очередь визуальные компоненты делятся на оконные и графические компоненты. К оконным компонентам относятся все элементы управления, инкапсулирующие стандартные элементы управления
ОС Windows. На базе графических компонентов реализованы элементы
управления, специализирующиеся на отображении какой-нибудь информации. Они не способны получать фокус ввода, но зато и не требовательны
к ресурсам компьютера.
При переносе значка визуального компонента с панели компонентов Delphi
на рабочую форму проекта с ним происходят чудесные метаморфозы: он
трансформируется в соответствующий орган управления (как метка Label
в примере «Привет, Мир!»). Внешний вид невизуального компонента в этом
случае не претерпевает никаких изменений. В режиме проектирования он

Основа основ – класс TObject

115

по-прежнему смотрится точно так же, как на палитре компонентов. Можете
проверить сами, перенеся на форму компонент TTable или TTimer. Невизуальные компоненты будут выполнять скрытую работу в недрах вашего приложения и на этапе выполнения проекта не видны.
Все компоненты Delphi обладают рядом общих свойств и методов. Вы уже
знаете, что в этом повинен один из трех китов ООП – наследование. Гены
предков сказываются в дочерних классах. Иногда, когда потомков не устраивают унаследованные свойства и методы, они их перекрывают (видоизменяют). В этом случае на их стороне другой кит ООП – полиморфизм.
Очень часто класс старается спрятать некоторые тонкости реализации методов и свойств, дабы не допустить некорректных действий со стороны программиста; это инкапсуляция. Вот такая, на первый взгляд не очень сложная, семейная жизнь классов.

Основа основ – класс TObject
Класс TObject возглавляет иерархию классов в Delphi. Другими словами, он
родоначальник всех классов. TObject – абстрактный класс; это означает, что
класс физически не способен стать элементом управления.
TObject не имеет никаких свойств. В его арсенале есть только методы, которые позволяют создавать новые объекты на основе базового класса и производить с ними простейшие манипуляции.
Поясним понятие абстрактного класса. Экземпляр такого класса не сможет
стать физическим объектом, таким как кнопка, строка ввода или панель. В абстрактном классе, как правило, лишь определяются заголовки необходимых методов,
а программный код их реализации отсутствует. Это в своем роде договор о намерениях. Таким образом, класс-владелец абстрактных методов принуждает своих
потомков описывать объявленные в нем методы.
На первый взгляд такой подход может показаться несколько абсурдным. Казалось
бы, разве сложно описать в классе и заголовок, и реализацию метода и тем самым
избавить от этой необходимости несчастных потомков. Но при более глубоком
рассмотрении видно, что лобовое решение – не самое лучшее.
Наглядный пример тому – абстрактный класс TdataSet, благодаря которому в Delphi
создана универсальная, абсолютно не зависящая от платформы система доступа
к набору данных, будь то BDE, ADO, Interbase и т. д. Для обращения к данным программист применяет одноименные методы потомков класса TDataSet, зачастую
даже не подозревая, что они наполнены разным содержанием.

Класс TObject решает следующие задачи:
• Создание, поддержка и уничтожение объекта. Распределение, инициализация и освобождение памяти, необходимой для этого объекта.
• Осуществление надстройки над информацией о типе времени выполнения (runtime type information, RTTI).
• Поддержка взаимодействия объекта с внешней средой.

116

Глава 6. Невидимые классы

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

Конструктор предназначен для создания нового экземпляра класса, инициализации памяти и обработчика исключительных ситуаций. Конструктор самостоятельно вызывает метод:
class function NewInstance: TObject; virtual;

Именно NewInstance() и создает новый объект. В противовес конструктору,
дарующему объекту жизнь, существует его антипод – специальный метод,
именуемый деструктором:
destructor Destroy; virtual;

Деструктор обеспечивает уничтожение экземпляра класса, без сожаления
освобождая ресурсы, используемые экземпляром. Для этого внутри деструктора вызывается метод:
procedure FreeInstance; virtual;

Логическим развитием деструктора считается метод:
procedure Free;

Процедура выполняет те же самые функции разрушения объекта, но в дополнение к этому перед вызовом деструктора проверяет сам факт существования уничтожаемого объекта, что позволяет избежать ошибок при освобождении ресурсов, используемых объектом.
Для уничтожения объекта вместо прямого вызова деструктора Destroy() рекомендуется воспользоваться процедурой Free().
При завершении работы приложения оно старается автоматически уничтожить
все созданные в нем объекты. Однако существует правило, гласящее, что программист должен сам уничтожить все созданные им объекты.

Информация о классе
В классе TObject реализован ряд низкоуровневых методов, нацеленных на
информирование об экземпляре класса. В первую очередь к ним относятся
так называемые методы класса (class function). Метод
class function ClassInfo: Pointer;

вернет указатель на RTTI – структуру, содержащую информацию о классе.

117

Основа основ – класс TObject

Особенность метода класса в том, что он вызывается как обычная процедура –
без создания экземпляра класса, которому принадлежит этот метод.

Существуют и другие методы, поясняющие программисту, с объектом какого класса он имеет дело. Для того чтобы спросить объект, не является ли он
объектом определенного класса, применяйте метод:
class function ClassNameIs(const Name: string): Boolean;

В качестве параметра Name передают название класса, например Tbutton. Если объект действительно является экземпляром класса TButton, функция
вернет true. Следует особо отметить, что этот метод не контролирует всю цепочку наследования. Он видит только класс этого объекта.
Текстовое название класса возвратит функция:
class function ClassName: ShortString;

//строка с названием класса

Еще более полезный метод:
function ClassType: TClass;

//описание класса

Результирующее значение, возвращаемое методом ClassType(), вполне может использоваться в конструкциях приведения типа (операторы as и is):
if MySecondObject is MyFirstObject.ClassType then
MySecondObject as MyFirstObject.ClassType do …

Любой из объектов (за исключением TObject) знает своего непосредственного
предка, на основе которого он был создан. Следующая функция возвратит
описание класса-предка:
class function ClassParent: TClass;

Возможно решение и обратной задачи: проверить, не является ли класс AClass
предком нашего объекта. В случае успеха функция возвращает true.
class function InheritsFrom(AClass: TClass): Boolean;

Для более глубоких исследований объекта могут пригодиться функции, информирующие нас о полях и методах объекта, опубликованных в его секции
published:
function FieldAddress(const Name: ShortString): Pointer;
class function MethodAddress(const Name: ShortString): Pointer;
class function MethodName(Address: Pointer): ShortString;

Функция FieldAddress возвращает указатель на поле объекта, а функция
MethodAddress – указатель на метод объекта. В качестве параметра следует передать строку с названием поля или метода соответственно. Функция MethodName решает обратную задачу: возвращает название метода при помощи указателя.
Поместите на форму три кнопки TButton и многострочный текстовый редактор – компонент TMemo. В процедуре обработки события OnClick() первой
и второй кнопки напишите одну единственную строку:

118

Глава 6. Невидимые классы

procedure TForm1.Button1Click(Sender: TObject);
begin
MessageBeep(0);
end;

Это что-то вроде строки-пустышки, заставляющей компьютер издавать системный звук. А обработку события щелчка по третьей кнопке повторите
в соответствии с нижеизложенным листингом:
procedure TForm1.Button3Click(Sender: TObject);
var i : Integer;
s : String;
p : Pointer;
begin
Screen.Cursor:=crHourgLass;
Memo1.Clear;
for i:=1 to High(integer)-1 do
{Это очень длинный цикл, т. к. High(Integer)-1 возвратит число 2147483646}
begin
p:=Ptr(i);
s:=TForm1.MethodName(p);
if s<>'' then Memo1.Lines.add(Format('%d %s',[i, s]));
end;
Screen.Cursor:=crDefault;
end;

Запустите программу и запаситесь терпением. В весьма длинном цикле перебираются все неотрицательные значения, которые способна принимать
переменная i. Указатель на целочисленное значение i мы получаем при помощи функции Ptr(), а затем спрашиваем у представителя класса TForm1
главной формы проекта, есть ли у него метод (функция MethodName) с таким
указателем, и если таковой имеется, то выводим его адрес и название в поле
Memo. Если все сделано верно, то в результате выполнения программы в memo-поле окажутся две строки – Button1Click и Button3Click.
А в следующем листинге продемонстрирован способ вызова найденного метода. Функция MethodAddress() пытается найти процедуру с именем Button1Click(). И если такая процедура обнаружена, то осуществляется ее вызов.
procedure TForm1.Button2Click(Sender: TObject);
var p : pointer;
Button1Click : procedure(Sender: TObject);
begin
p:=TForm1.MethodAddress('Button1Click');
if Assigned(p)=true then
begin
@Button1Click:=p;
Button1Click(sender);
end;
end;

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

Класс TPersistent

119

менения в событие, возникающее при щелчке по первой кнопке, и вновь попытайтесь вызвать его щелчком по кнопке Button2.
procedure TForm1.Button1Click(Sender: TObject);
begin
//MessageBeep(0);
Button1.Caption:='';
end;

Delphi известит об ошибке доступа к некоторому адресу. Другими словами,
ошибка возникает при обращении к свойствам и методам объектов.

Поддержка COM
В завершение приведем несколько методов для программистов, имеющих
представление о модели многокомпонентных объектов (Component Object
Model, COM). Функция:
function GetInterface(const IID: TGUID; out Obj): Boolean;

возвратит интерфейс, определенный в уникальном идентификаторе IID. Результат будет помещен в выходной параметр Obj.
Есть еще две функции класса, позволяющие собрать данные о всех полях
и методах данного объекта в единую таблицу интерфейсов (точнее структуру
TInterfaceTable):
class function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry;
class function GetInterfaceTable: PInterfaceTable;

где PInterfaceTable – указатель на описанную в модуле System структуру
TInterfaceTable.
type PInterfaceTable = ^TInterfaceTable;
TInterfaceTable = packed record
EntryCount: Integer;
Entries: array[0..9999] of TInterfaceEntry;
end;

Класс TPersistent
Основная задача абстрактного класса TPersistent – научить создаваемые из
него объекты сохранять и загружать свое описание из потока (файла или
специально зарезервированной области памяти). Благодаря этому все элементы управления способны взаимодействовать с файлом *.dfm, а программист получает возможность сохранять все изменения, сделанные в проекте.
Как и в случае с TObject, основу рассматриваемого класса составляют методы. К наиболее заслуживающим внимание методам TPersistent стоит отнести методы присвоения данных:
procedure Assign(Source: TPersistent); virtual;
procedure AssignTo(Dest: TPersistent); virtual;

120

Глава 6. Невидимые классы

Метод Assign() позволяет элементу управления присваивать себе данные,
связанные с другим объектом. Например, Image2, специализирующийся на
хранении и отображении графической информации, получит доступ к картинке, содержащейся в элементе управления Image1, следующим образом:
Image2.Picture.Assign(Image1.Picture);

Вполне естественно, что особенности реализации метода Assign() изменяются от класса к классу, поэтому у потомков TPersistent процедура Assign()
вполне может быть переписана, или, как часто говорят, перекрыта. Метод
AssignTo() – близкий приятель метода Assign(). С его помощью элемент
управления сможет разделить свои данные с другим объектом. Например,
специализирующийся на отображении графических файлов компонент Image1 (класс TImage) может поделиться загруженным в него изображением
с коллегой по цеху – компонентом Image2.
Image1.Picture.AssignTo(Image2.Picture);

Важно запомнить, что метод AssignTo() не создает дубликата каких-то данных, а лишь разделяет их с другим объектом.

Поток – TStream
Абстрактный класс TStream описан в модуле Classes и предназначен для организации чтения и записи данных во внешний источник (файл, память
и т. д.). Такие действия называются операциями ввода-вывода. Поток обладает всего двумя свойствами:
property Size: Longint;
property Position: Longint;

Размер Size измеряется в байтах; попытка редактировать значение свойства
в родительском классе не даст никаких результатов. Такая возможность
предоставлена только в потомках TStream. Свойство Position возвращает текущую позицию внутри потока во время операций чтения и записи. За чтение данных потока отвечает метод:
function Read(var Buffer; Count: Longint): Longint; virtual; abstract;

А за запись данных потока метод:
function Write(const Buffer; Count: Longint): Longint; virtual; abstract;

Но если вы хотите контролировать появление ошибок при чтении и записи
данных, то опирайтесь на методы:
procedure ReadBuffer(var Buffer; Count: Longint);
procedure WriteBuffer(const Buffer; Count: Longint);

Данные процедуры – это надстройки над методами Read() и Write(). При невыполнении условий – (Count <> 0) and (Read(Buffer, Count) <> Count) для ReadBuffer или (Count <> 0) and (Write(Buffer, Count) <> Count) для WriteBuffer – создаются исключительные ситуации EReadError и EWriteError соответственно.

Основа компонента – класс TComponent

121

Для копирования данных из одного потока в другой предназначен метод:
function CopyFrom(Source: TStream; Count: Longint): Longint;

где Source – поток-источник, а Count – количество копируемых байт. При необходимости полностью скопировать поток в качестве параметра Count передается значение 0. Функция возвращает количество скопированных байт.
Следующий метод обеспечивает изменение текущей позиции потока
(табл. 6.1):
function Seek(Offset: Longint; Origin: Word): Longint; virtual; abstract;

Таблица 6.1. Допустимые значения параметра Origin
Значение Origin

Результат выполнения

soFromBeginning ( = 0)

Смещается на Offset байт от начала потока.

soFromCurrent ( = 1)

Смещается на Offset байт от предыдущего положения.

soFromEnd ( = 2)

Смещается на Offset (Offset<=0) байт от конца потока.

Основа компонента – класс TComponent
Все элементы управления палитры компонентов Delphi берут начало от абстрактного класса TComponent. В процессе программирования вам вряд ли когда-нибудь понадобится этот класс в его первозданном виде. Однако при работе с его потомками вы гарантированно столкнетесь с унаследованными
от него свойствами и методами. Самое главное, что дарит TComponent своим
наследникам, – это свое незапятнанное имя:
property Name: TComponentName;
type TComponentName: string;

Каждый элемент управления, используемый в проекте Delphi, обязан иметь
уникальное имя. Это имя присваивается автоматически в момент переноса
элемента управления с палитры компонентов Delphi на поверхность рабочей
формы проекта. Процесс назначения элементу управления нового имени
весьма прост: берется имя класса (допустим, TButton), убирается первый символ «T» и к окончанию имени добавляется порядковый номер элемента
управления этого типа на форме. Так, первая кнопка, размещенная на форме, получит название Button1, вторая – Button2…
В процессе конструирования интерфейса приложения избегайте помощи Delphi
и старайтесь каждому компоненту присваивать имя самостоятельно. Название
должно характеризовать функциональное назначение элемента управления. Рекомендуемое правило: первая часть имени включает сокращение от названия класса
компонента, а вторая – задачу, решаемую компонентом. Например: кнопка закрытия формы будет названа так:
btn (от класса TButton) + Close (закрыть) = btnClose

главная форма проекта:
frm (от TForm) + Main (главный) = frmMain

122

Глава 6. Невидимые классы

Следуя предложенному соглашению, вы значительно повысите читаемость листинга программы и упростите процесс изучения кода другими программистами.

У каждого компонента опубликовано свойство, никогда не используемое
системой, но часто применяемое программистами для дополнительной идентификации объекта или хранения связанных с объектом целочисленных
значений:
property Tag: Longint;

Практически ни один компонент не может существовать без своего владельца (owner). По умолчанию владельцем всех компонентов выступает форма,
на которой они размещены программистом.
property Owner: TComponent;

Одна из обязанностей владельца – истребление подчиненных ему объектов. Такое
поведение компонента упрощает работу программиста: достаточно дать команду
на уничтожение владельца, а тот уже сам «расправится» с принадлежащими ему
объектами.

Для создания новых объектов используйте конструктор:
constructor Create(AOwner: TComponent);

Как видите, объявление конструктора у TComponent несколько отличается
от объявления конструктора у TObject. Благодаря новому параметру AOwner
устанавливается связь компонента с его владельцем.
MyButton:=TButton.Create(Form1);

За уничтожение компонента ответственен его деструктор:
destructor Destroy; override;

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

Компоненты, принадлежащие владельцу, хранятся в списке владельца:
property Components[Index: Integer]: TComponent;

Каждый компонент в списке владельца обладает индивидуальным индексом:
property ComponentIndex: Integer;

Для того чтобы узнать общее количество компонентов, принадлежащих владельцу, воспользуйтесь свойством:
property ComponentCount: Integer;

Попробуем на практике. Откройте новый проект. Поместите на форму проекта десяток кнопок (компонент Button из палитры Standard). Затем выберите
любую из них и в Инспекторе объектов измените ее имя на btnStart, а свойст-

Основа компонента – класс TComponent

123

ву tag присвойте 1. Дважды щелкните по btnStart и опишите процедуру OnClick() так, как это предложено в следующем листинге:
procedure TForm1.btnStartClick(Sender: TObject);
var i : integer;
begin
for i:= Form1.ComponentCount-1 downto 0 do
if (Form1.Components[i] is TButton) and (Form1.Components[i].Tag<>1)
then
with (Form1.components[i] as TButton) do Visible:=NOT Visible;
end;

Все просто. Цикл for..downto перебирает список компонентов, владельцем
которых выступает форма Form1, и если компонент принадлежит классу
TButton и его свойство tag не равно 1, то он становится невидимым.
Ряд методов позволяет манипулировать списком компонентов, для которых
TControl выступает владельцем. Удаление компонента из списка обеспечивается процедурой:
procedure RemoveComponent(AComponent: TComponent);

Полная очистка списка:
procedure DestroyComponents;

Вставка в конец списка нового компонента:
procedure InsertComponent(AComponent: TComponent);

Поиск компонента-потомка в списке:
function FindComponent(const AName: string): TComponent;

Роль класса TComponent в период разработки компонента
Знания о текущем состоянии компонента пригодятся в процессе создания
компонентов – потомков класса TControl (см. главу 16 «Создание компонентов»). Состояние описывается свойством:
property ComponentState: TComponentState;

Таблица 6.2. Допустимые состояния компонента TComponentState
Флаг

Состояние компонента

csAncestor

Компонент помещается на форму. Имеет значение, если установлен флаг csDesigning.

csDesigning

Компонент находится на форме в момент разработки.

csDestroying

Компонент готовится к разрушению.

csFixups

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

csFreeNotification Компонент отправляет уведомление другим элементам о том, что
он переходит в стадию уничтожения.

124

Глава 6. Невидимые классы

Таблица 6.2 (продолжение)
Флаг

Состояние компонента

csInline

Элемент управления верхнего уровня, вполне доступный для редактирования. Флаг применяется для идентификации требуемых фреймов в момент загрузки и сохранения.

csLoading

Осуществляется загрузка компонента. Флаг будет включен до тех пор,
пока не прекратится процесс загрузки (метод Loaded).

csReading

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

csUpdating Компонент начинает обновляться для того, чтобы отразить изменения в форме предка. Флаг может быть установлен, если установлен флаг csAncestor.
csWriting

Компонент осуществляет запись значений своих свойств в поток.

С помощью приведенного ниже кода можно проверить, находится ли компонент в стадии разработки:
INHERITED Create(aOwner)
if (csDesigning in ComponentState)=false then
begin
{компонент в стадии разработки}
end;

Также существенную роль в процессе проектирования новых компонентов
играют две процедуры:
procedure Notification(AComponent: TComponent; Operation: TOperation); virtual;
procedure FreeNotification(AComponent: TComponent);

Вызов процедур во время выполнения приложения не имеет смысла. Метод
Notification() автоматически вызывается IDE Delphi в момент размещения
на форме компонента или удаления его с формы. Метод имеет два параметра, первый из которых указывает на компонент, добавляемый или убираемый с формы, а второй содержит код операции:
type TOperation = (opInsert {вставка}, opRemove {удаление});

Как правило, при разработке новых компонентов программисты переопределяют поведение этого метода для компонентов, ссылающихся на другие
компоненты, расположенные на форме. В качестве примера компонента такого рода можно предложить источник данных TDataSource. Этот элемент
управления взаимодействует с набором данных (property DataSet: TDataSet),
в качестве которого могут выступать TTable, TQuery и подобные им элементы.
Задача метода FreeNotification – уведомить связанный компонент (например, TDataSource и TTable) о факте освобождения занимаемых ресурсов.
Следующие два свойства применяются в компонентах, поддерживающих
многокомпонентную модель объектов (COM, Component Object Model):
property ComObject: IUnknown;
property VCLComObject: Pointer;

Элемент управления – класс TControl

125

Элемент управления – класс TControl
Класс TControl – это следующий шаг в недра библиотеки VCL. Запомните главное: абстрактный класс TControl порождает все визуальные компоненты
Delphi. А визуальный компонент – это элемент управления (кнопка, полоса
прокрутки, поле ввода), обязанный реагировать на внешние воздействия.
Вклад класса Tcontrol в библиотеку визуальных компонентов просто грандиозен. При его изучении мы познакомимся с первыми обработчиками событий (events). Визуальные компоненты наследуют от TControl свойства, методы и события, связанные с установкой местоположения компонента на
форме, с видом курсора, прорисовкой, всплывающей подсказкой, с откликами на события мыши и операциями drag-and-drop (перетащить и отпустить)
и drag-and-dock (буксировка).

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

записывается имя контейнера. В качестве контейнера может выступать потомок класса TWinControl; обычно это форма или панель.
Прежде чем говорить о размерах и местоположении объекта, разберемся
с единицами измерений и системой координат. В отличие от географов, оперирующих километрами, и инженеров, в качестве единиц измерений отдающих предпочтение миллиметрам, программисты для измерения расстояний
используют пиксел. Это размер минимальной точки экрана, в первую очередь зависящий от физических характеристик монитора и выбранного разрешения.
По умолчанию за начальную точку отсчета с координатами X=0, Y=0 принимается левый верхний угол клиентской части формы (рис. 6.2). Местопоleft
0, 0

width

height

top

X (пикс.)

Y (пикс.)

Рис. 6.2. Размер и положение элемента управления
в клиентских координатах формы

126

Глава 6. Невидимые классы

ложение и размеры элемента управления, размещаемого на форме, хранятся в свойстве:
property BoundsRect: TRect;

где:
TRect = recordcase Integer of
0: (Left, Top, Right, Bottom: Integer);
(TopLeft, BottomRight: TPoint);end;
type TPoint = record
X: Longint;
Y: Longint;
end;

1:

Более удобный доступ к размерам визуального компонента по вертикали
и горизонтали соответственно обеспечивают свойства:
property Height: Integer;
property Width: Integer;

Расстояние по горизонтали от левого края формы до верхнего левого угла
компонента устанавливается в свойстве:
property Left: Integer;

а расстояние от верхнего среза клиентской части формы до левого верхнего
угла компонента в свойстве:
property Top: Integer;

Программист вправе предусмотреть систему ограничений на размеры элемента управления с помощью свойства:
property Constraints: TSizeConstraints;

Здесь можно установить ограничения на вертикальный и горизонтальный
размеры элемента управления: MaxHeight, MaxWidth, MinHeight и MinWidth.
Размеры и местоположение клиентской области окна элемента управления
можно узнать из свойства:
property ClientRect: TRect;

//только для чтения

Для переопределения размеров клиентской области элемента управления
воспользуйтесь свойствами:
property ClientHeight: Integer;
property ClientWidth: Integer;

Нередко возникает необходимость узнать местоположение определенной
точки не в клиентских координатах (скажем, формы), а в глобальных аппаратных координатах устройства, например экрана монитора. Специалистом
в этой области выступает метод:
function ClientToScreen(const Point: TPoint): TPoint;

Для решения обратной задачи – преобразования аппаратных координат определенной точки экрана к клиентским координатам – предназначен метод:
function ScreenToClient(const Point: TPoint): TPoint;

127

Элемент управления – класс TControl

Выравнивание элемента управления
В классе TControl опубликовано свойство, отвечающее за выравнивание элемента управления, располагаемого в клиентской области контейнера, например формы:
property Align: TAlign;

По умолчанию свойство отключено (Align=alNone). В табл. 6.3 указаны возможные значения, которые может принимать свойство Align.
Таблица 6.3. Возможные значения типа TAlign
Значение

Результат

alNone

Выравнивание отключено.

alTop

Элемент управления займет всю верхнюю часть контейнера. Попытки
изменить свойства width и top не принесут результатов.

alBottom

Компонент переместится в нижнюю часть контейнера. Свойство width
недоступно.

alLeft

Компонент займет все свободное пространство в левой части контейнера. Свойства top, left и height недоступны.

alRight

Элемент управления займет правую часть контейнера. Свойство height
недоступно.

alClient

Компонент займет всю свободную клиентскую часть контейнера. Свойства top, left, height и width недоступны.

Достоинство свойства align заключается в том, что при изменении размеров контейнера-владельца элемент управления растягивается (сжимается) вместе с ним.
Существует возможность «привязки» элемента управления
не только к левому верхнему
углу клиентской части контейнера:

alClient

alRight

alTop
alLeft
alBottom

Рис. 6.3. Пример использования свойства align

property Anchors: TAnchors;
type TAnchors = set of TAnchorKind;
type TAnchorKind = (akTop, akLeft, akRight, akBottom);

Свойство Anchors определяет способ позиционирования компонента относительно четырех сторон его контейнера. Например, установим Anchors = [akRight, akBottom], тогда при изменении размеров контейнера местоположение
элемента управления будет неизменным относительно правой (akRight)
и нижней (akBottom) сторон.

128

Глава 6. Невидимые классы

Если с помощью Anchors осуществить привязку элемента управления ко всем четырем сторонам контейнера, то при изменении размеров контейнера элемент
также изменит все свои размеры.

Видимость и активность элемента управления
Каждый элемент управления обладает свойством
property Visible: Boolean;

позволяющим включить (true) или отключить (false) его отображение. Такую же операцию осуществляют две процедуры
procedure Show;
procedure Hide;

// аналог visible := true;
// аналог visible := false;

Необходимым условием видимости элемента управления является видимость контейнера-владельца, на котором размещен этот элемент.

Если два элемента управления расположены один над другим (перекрывают
друг друга), то достать элемент на поверхность или спрятать его помогают
методы:
procedure BringToFront; //переместить вперед
procedure SendToBack;
//переместить назад

Методы BringToFront() и SendToBack() доступны и во время проектирования. Для
их вызова щелкните правой кнопкой мыши по компоненту и выберите соответствующий пункт контекстного меню.

Ответственность за активность элемента несет свойство:
property Enabled: Boolean;

При установке Enabled в false элемент управления перестает реагировать
на клавиатуру, мышку и даже на мольбы начинающего пользователя.

Отображение текста
Практически любому элементу управления предоставлено право отображать, а иногда и редактировать связанные с ним текстовые данные. Текст
может содержаться в свойстве Caption (заголовок):
type TCaption = string;
property Caption: TCaption;

либо в свойстве Text:
property Text: TCaption;

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

Элемент управления – класс TControl

129

типа кнопки, метки, панели. Свойство Text указывает, что элемент управления в состоянии не только выводить текст на экран, но и предоставляет
пользователю услуги по его редактированию. Это свойство есть у строк ввода
и полей Memo.
Свойством Caption вооружены кнопки и пункты меню. Для подчеркивания любого
символа из Caption этих элементов управления вставьте перед ним символ &. Например:
btnFileOpen.Caption:= '&Открыть файл';

Таким образом определяется горячая клавиша, на которую будет реагировать элемент управления. Теперь при одновременном нажатии клавиш Alt и подчеркнутого
символа «О» будет вызван обработчик события для этого элемента управления.

Внешний вид
За цвет элемента управления отвечает свойство Color:
property Color: TColor;

Тип шрифта, отображаемого в Caption или Text, изменяется при помощи
свойства:
property Font: TFont;

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

Два свойства:
property ParentColor: Boolean;
property ParentFont: Boolean;

разрешают (true) или запрещают (false) использовать настройки цвета
и шрифта родительского элемента управления. По умолчанию они принимают значение true, что позволяет придавать вашему приложению профессиональный вид.

Всплывающая подсказка
Использование всплывающей подсказки Hint придает интерфейсу программного продукта дружественный вид. Подсказка появляется в момент
остановки курсора мыши над элементом управления. Текст подсказки задается в свойстве:
property Hint: string;

Подсказка будет отображена только в том случае, если следующее свойство
элемента установлено в true:
property ShowHint: Boolean;

130

Глава 6. Невидимые классы

Для централизованного включения режима демонстрации подсказок для всех компонентов обычно применяют свойство ParentShowHint формы, на которой они размещены.
Подсказка Hint может состоять из двух частей, которые отделяются друг от друга символом вертикальной черты «|». Некоторые особенности вывода на экран
второй части всплывающей подсказки продемонстрированы в главе 9 «Форма, интерфейсы MDI и SDI» в разделе, посвященном классу TApplication.

Взаимодействие с командным объектом
При разработке проектов большой и средней степени сложности (в особенности приложений с серьезным пользовательским интерфейсом) для централизации управления приложением программисты Delphi часто применяют
особый вид невизуальных элементов управления, называемых командами.
Этот тип объектов строится на основе класса TBasicAction и будет подробно
рассмотрен в главе 17 «Централизованное управление приложением», а пока отметим наличие у потомков класса TControl специального свойства:
property Action : TBasicAction;

предназначенного для связи обычного элемента управления с командой TBasicAction. Как только связь установлена, элемент управления TControl начинает работать в интересах командного объекта. Его заголовок, всплывающая подсказка, пиктограмма изменяются в соответствии с настройками команды. Но самое главное в том, что подключенный к команде элемент
управления может стать инициатором вызова ключевого события командного компонента OnExecute().
Хотя с момента установления связи с командным объектом наш элемент
управления превращается в своего рода слугу этой команды, тем не менее он
имеет право немножко поруководить своим «хозяином». Так, для инициирования события OnExecute() командного объекта компонент вызовет функцию:
function ExecuteAction(Action: TBasicAction): Boolean; dynamic;

Для принудительной установки связанного с компонентом командного объекта в актуальное, соответствующее текущей обстановке состояние можно
вызвать метод:
function UpdateAction(Action: TBasicAction): Boolean; dynamic;

Всплывающее меню
С каждым визуальным компонентом может быть сопоставлено всплывающее (контекстное) меню, появляющееся по щелчку правой кнопкой мыши.
property PopupMenu: TPopupMenu;

Более подробную информацию о меню см. в главе 8 «Стандартные компоненты».

Владелец файла:
Александр Аносов, mylex87@yandex.ru
Сообщить о нелицензионном использовании: http://www.books.ru/post_form/newform.php

Элемент управления – класс TControl

131

Свойства и методы Tcontrol, используемые
в период разработки компонентов
О текущем состоянии элемента во время выполнения приложения (run time)
можно узнать из свойства:
property ControlState: TControlState;

В табл. 6.4 приведены возможные значения флагов.
Таблица 6.4. Описание TControlState
Флаг

Состояние элемента управления

csLButtonDown

Над элементом удерживается в нажатом состоянии левая кнопка
мыши.

csClicked

Если элемент способен воспринимать щелчки мышью, нажатие
кнопки мыши интерпретируется как щелчок. (В свойстве ControlStyle должен быть установлен флаг csClickEvents.)

csPalette

Получено сообщение WM_PALETTCHANGED непосредственно элементом управления либо одним из его владельцев.

csReadingState

Элемент загружается из потока.

csAlignmentNeeded

Элемент должен выровняться относительно своего контейнеравладельца.

csFocusing

Элемент получает фокус ввода (но это не гарантирует, что он останется сфокусированным).

csCreating

Элемент и/или его владелец и субэлементы в стадии создания.
По завершении флаг очищается.

csPaintCopy

Элемент скопирован и начата прорисовка его копии. Для этого
в ControlStyle должен быть установлен флаг csReplicatable.

csCustomPaint

Элемент находится в стадии перерисовки при обработке частных
задач.

csDestroyingHandle Освобождается указатель на элемент.
csDocking

Элемент переносится (операция Docking).

Об особенностях стиля элемента управления можно узнать из набора флагов
в свойстве:
property ControlStyle: TControlStyle;

При разработке нового компонента, как правило, в его конструкторе устанавливаются флаги (табл. 6.5), определяющие его поведение.
Таблица 6.5. Описание TControlStyle
Флаг

Элемент управления

csAcceptsControls

Может служить контейнером для других элементов управления.

csCaptureMouse

Способен отслеживать сообщения мыши.

132

Глава 6. Невидимые классы

Таблица 6.5 (продолжение)
Флаг

Элемент управления

csDesignInteractive Во время разработки транслирует нажатия правой кнопки мыши в нажатия левой.
csClickEvents

Воспринимает щелчки мышью.

csFramed

Имеет трехмерное обрамление.

csSetCaption

Пока caption (заголовок) не установлен разработчиком, в него
будет записано Name (имя) элемента управления.

csOpaque

Полностью занимает клиентскую область родителя.

csDoubleClicks

Воспринимает двойные щелчки мышью.

csFixedWidth

Имеет фиксированную ширину независимо от масштабирования.

csFixedHeight

Имеет фиксированную высоту независимо от масштабирования.

csNoDesignVisible

Невидим в период разработки.

csReplicatable

Может быть скопирован и нарисован в другой области с использованием метода PaintTo.

csNoStdEvents

Стандартные события мыши и клавиатуры игнорируются.

csDisplayDragImage Способен отображать изображение из списка в момент перетаскивания.
csReflector

Реагирует на сообщения Windows, на получение фокуса и изменения размера. Обычно используется при построения элементов
управления ActiveX.

csActionClient

Связан с компонентом, в ассортименте которого есть свойство
Action. Флаг установлен, если это свойство определено.

csMenuEvents

Отвечает на события системного меню.

Оконный элемент управления – класс TWinControl
Абстрактный класс TWinControl описан в модуле Controls. У класса выделяются три ключевых особенности:
1. Наличие дескриптора окна – связующего звена с функциями Windows API.
2. Способность получать фокус ввода.
3. Возможность выступать в качестве контейнера для других элементов
управления.
Приставкой Win разработчики класса акцентируют внимание на том, что
TWinControl является предтечей всех оконных элементов управления в библиотеке визуальных компонентов. Это одна из важнейших характеристик
элемента управления. Ведь каждый оконный элемент управления (кнопка,
панель, строка ввода) является полноправным окном Windows.
Если для вас это новость, то повторюсь: все оконные элементы управления
создаются на основе окна – класса TWndClass. Можно написать десятки про-

Оконный элемент управления – класс TWinControl

133

грамм на Delphi и не подозревать об этом. Средства, предоставляемые в VCL,
на первых порах позволяют превосходно обходиться без таких знаний,
но тогда даже не стоит думать о программировании на профессиональном
уровне. Подробнее об этом мы поговорим в главе 27 «Программирование
на Win32 API».
Каждый оконный элемент управления имеет свой указатель (handle):
property Handle : HWND;

//только для чтения

Программисты на С тип данных HWND1 называют указателем окна. Физически он представляет собой обычное число типа LongWord (длиной 32 разряда).
Значение, хранящееся в дескрипторе, служит уникальной ссылкой на некую структуру в памяти компьютера, описывающую окно. Создавая оконный элемент управления, Microsoft® Windows автоматически заполняет
и хранит указатель этого объекта. Благодаря такой незримой нити, операционная система всегда знает, как добраться до нужного окна.
Никогда не пытайтесь изменять значение, хранящееся в свойстве Handle. Указатель – это нить, связывающая оконный элемент управления с операционной системой Windows.

Хорошим правилом при обращении к оконному элементу управления по его
указателю является проверка его дескриптора:
function HandleAllocated: Boolean;

Наличие дескриптора свидетельствует о существовании самого объекта. В таком случае функция возвратит значение true. За выделение дескриптора
оконному элементу управления во время его создания отвечает процедура:
procedure HandleNeeded;

Создание окна
Обсудим несколько внутренних методов класса TWinControl, обеспечивающих создание экземпляра окна. В момент создания оконного элемента
управления автоматически вызывается метод:
procedure CreateWnd;

отвечающий за формирование указателя окна этого элемента. Для этого им
вызывается метод:
procedure CreateParams(var Params: TCreateParams);

Эта процедура инициализирует параметры окна (стиль, размеры и местоположение, заголовок, дескриптор родительского окна и т. д.) и регистрирует
окно с такими параметрами в системе. После успешной регистрации выполняется процедура:
procedure CreateWindowHandle(const Params: TCreateParams);
1

HWND – сокращение от handle to a window – указатель на окно.

134

Глава 6. Невидимые классы

С целью создания окна CreateWindowHandle() передает параметры функции
Win 32 API – CreateWindowEx().

Управление подчиненными компонентами
Важной особенностью потомков класса TWinControl считается возможность выступать в роли родителей других элементов управления (см. свойство Parent
в описании класса TControl). Общее количество этих элементов содержится
в свойстве:
property ControlCount: Integer;

Дочерние элементы хранятся в списке; доступ к каждому элементу производится по его порядковому номеру:
property Controls[Index: Integer]: TControl;

Следующий пример демонстрирует способ сбора имен всех дочерних элементов формы Form1. Выявленные имена заносятся в многострочное поле для
хранения текста memo1:
for i:=0 to Form1.ControlCount-1 do memo1.Lines.Add(Form1.Controls[i].Name);

Проверка наличия элемента в списке осуществляется методом:
function ContainsControl(Control: TControl): Boolean;

В случае успеха функция возвратит значение true. Состав списка можно редактировать с помощью двух методов:
procedure InsertControl(AControl: TControl); // вставка элемента в список
procedure RemoveControl(AControl: TControl); // удаление элемента

Любой потомок TWinControl способен проверить наличие элемента управления в точке с клиентскими координатами Pos:
function ControlAtPos(const Pos: TPoint; AllowDisabled: Boolean): TControl;

Параметр AllowDisabled разрешает или запрещает поиск среди элементов
в зависимости от состояния их свойства enabled. Если компонент обнаружен,
то функция его возвращает. В случае неудачи результат поиска равен nil.
Родительский оконный элемент управления способен разослать всем элементам своего списка сообщение Message:
procedure Broadcast(var Message);

Фокус ввода
В среде Microsoft® Windows одновременно могут выполняться несколько
приложений, каждое из которых способно располагать несколькими окнами. В свою очередь на окнах располагаются дочерние оконные элементы
управления (кнопки, строки ввода, различные списки выбора и т. п.). Все
эти окна и «окошечки» превосходно умеют реагировать на нажатия клавиш
клавиатуры, перемещения мыши и на щелчки ее кнопками. Вместе с тем на-

Оконный элемент управления – класс TWinControl

135

жатие клавиши «A» ни в коем случае не приведет к тому, что во всех компонентах, умеющих обрабатывать текст, одновременно появится символ «A».
Причина в том, что операционная система передает воздействие от клавиатуры или мыши только элементу, находящемуся в фокусе ввода.
Наличие фокуса ввода свидетельствует о том, что элемент управления находится в состоянии готовности к получению и обработке входящих сообщений. Одновременно в фокусе ввода может находиться только один элемент
управления, причем его родительская форма при этом обязана быть активной. Для проверки нахождения оконного элемента управления в фокусе
предназначен метод:
function Focused: Boolean;

// возвращает true в случае успеха

Обычно порядок смены фокуса между элементами управления определяется во время проектирования. Для этого необходимо щелкнуть правой кнопкой мыши по родительскому оконному элементу и выбрать пункт Tab Order… всплывающего меню.

Особо нетерпеливый элемент управления способен потребовать от системы
предоставления ему фокуса ввода:
procedure SetFocus;

Однако в некоторых случаях получение фокуса ввода оконным элементом
управления попросту невозможно. Такая ситуация возникает, когда объект
расположен на неактивном окне или вообще физически отключен (Enabled=
false). Если при таких обстоятельствах мы попытаемся программным образом передать фокус компоненту, то результатом станет генерация исключительной ситуации. Поэтому перед вызовом метода SetFocus следует с помощью метода:
function CanFocus: Boolean;

// в случае успеха возвращает true

предварительно проверить, может ли в сложившейся ситуации данный элемент управления получить фокус. Например:
if Button1.CanFocus=true then Button1.SetFocus;

События перехода фокуса происходят при щелчке мышью по компоненту
или при нажатии клавиши Tab. Процесс получения или потери фокуса ввода
сопровождается соответственно двумя событиями:
property OnEnter : TNotifyEvent;
property OnExit : TNotifyEvent;

//объект получает фокус ввода
//объект теряет фокус ввода

Все элементы управления, способные получать фокус ввода, содержатся
в особом списке, доступ к которому осуществляется с помощью процедуры:
procedure GetTabOrderList(List: TList);

Необходимым условием нахождения элемента в этом списке является установка в true его свойства:
property TabStop: Boolean;

136

Глава 6. Невидимые классы

Очередность перехода фокуса между элементами управления определяется
порядковым номером в свойстве:
property TabOrder: TTabOrder;
type TTabOrder = -1..32767;

Обработка событий в классах TControl и TWinControl
В классах TControl и TWinControl определена реакция на большую часть инициируемых пользователем событий. С некоторой степенью условности их
можно классифицировать как события получения/потери фокуса ввода, события манипулятора мышь, события клавиатуры, события перетаскивания
и буксировки. С событиями получения фокуса ввода мы уже познакомились, а теперь рассмотрим события мыши.

События манипулятора мышь
Наиболее распространенным событием мыши считается щелчок левой кнопкой над областью компонента. Обработчик события активизируется в момент освобождения левой кнопки мыши.
property OnClick: TNotifyEvent;

В предыдущих главах мы уже не раз приводили примеры обработки события OnClick(). Delphi генерирует следующий код:
procedure TForm1.Button1Click(Sender: TObject);
begin
{описание реакции на событие}
end;

Многие компоненты обрабатывают событие OnClick() при их активизации с клавиатуры:
– при нажатии клавиш управления курсором (клавиш со стрелками) в сетках, в списках выбора и ниспадающих списках;
– при нажатии горячих клавиш;
– при нажатии пробела, Enter для сфокусированных элементов;
– при щелчке по пункту меню.

Для реакции на двойной щелчок мышью предусмотрено событие:
property OnDblClick: TNotifyEvent;

Более широкие возможности предоставляют обработчики, раздельно контролирующие нажатие и отпускание кнопки мыши:
property OnMouseDown: TMouseEvent;
property OnMouseUp : TMouseEvent;

//кнопка нажимается
//кнопка отпускается

где:
TMouseEvent = procedure (Sender: TObject; Button: TMouseButton; Shift: TShiftState;
X, Y: Integer) of object;

Обработка событий в классах TControl и TWinControl

137

В табл. 6.6 представлен список передаваемых параметров, позволяющих
осуществлять тонкую настройку реакции на событие.
Таблица 6.6. Описание параметров типизированной процедуры TMouseEvent
Параметр Возможные значения
Sender
Button

Shift

X, Y

Описание

Ссылка на объект

Элемент управления – источник сообщения.

mbLeft

Щелчок левой кнопкой мыши.

mbRight

Щелчок правой кнопкой мыши.

mbMiddle

Щелчок центральной кнопкой мыши.

ssShift

Нажата клавиша Shift.

ssAlt

Нажата клавиша Alt.

ssCtrl

Нажата клавиша Ctrl.

ssLeft

Нажата левая кнопка мыши.

ssRight

Нажата правая кнопка мыши.

ssMiddle

Нажата центральная кнопка мыши.

ssDouble

Двойной щелчок любой кнопкой.

Integer

Возвращаются координаты курсора.

Взгляните на небольшой пример обработки события нажатия кнопки мыши.
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if (ssShift in Shift)
//если нажата клавиша Shift
and (Button=mbRight)
//и пользователь нажимает правую кнопку мышки
then Form1.caption := Format('X=%d Y=%d',[X,Y]);
end;

В заголовке формы выводятся координаты места нажатия правой кнопки
мыши при условии, что пользователем удерживается клавиша Shift.
При перемещении мыши возникает событие:
property OnMouseMove: TMouseMoveEvent;
TMouseMoveEvent = procedure(Sender: TObject; Shift: TShiftState;
X, Y: Integer) of object;

Основная задача OnMouseMove() – контролировать местоположение курсора
в клиентской части окна. Вставьте в обработчик события OnMouseMove() строку из предыдущего примера:
Form1.caption := Format('X=%d Y=%d',[X,Y]);

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

138

Глава 6. Невидимые классы
OnMouseDown() → OnMouseUp() → OnClick()

Двойной щелчок соответствует двум одинарным щелчкам.

Нередко программист сталкивается с задачей раздельной обработки одинарного и двойного щелчков. Дело в том, что двойной щелчок интерпретируется
системой как два одинарных, что зачастую вносит путаницу в работу приложения. Одним из методов, решающих подобную задачу, является контроль
за временем, проходящим между щелчками. Если за эталонный промежуток времени произошло два щелчка мышью, то обработка события OnClick()
должна прекратиться.
Время в миллисекундах, отводимое на двойной щелчок, можно получить
с помощью функции Windows API:
function GetDoubleClickTime : LongInt;
procedure TForm1.FormClick(Sender: TObject);
var Msg: TMsg; ControlTime: Longword;
begin
//Определение размера контролируемого отрезка времени
ControlTime := GetTickCount + GetDoubleClickTime;
//Ожидание повторного щелчка мышью на контролируемом отрезке
while GetTickCount < ControlTime do
if PeekMessage(Msg, Form1.Handle, WM_LBUTTONDBLCLK, WM_LBUTTONDBLCLK,
PM_NOREMOVE)
then Exit; // --> Выход из процедуры, т.к. это был двойной щелчок
//Продолжение обработки события, т.к. это был одинарный щелчок
end;

В приведенном выше листинге в цикле while..do обработчик события ожидает повторный щелчок мышью, откладывая обработку первого щелчка на
время ControlTime. Контроль за поведением кнопок мыши осуществляет
функция Windows API:
function PeekMessage(lpMsg: TMsg; h: hWnd; const wMsgFilterMin,
wMsgFilterMax, wRemoveMsg : Integer): LongBool;

В качестве параметров функции выступают: lpMsg – указатель на структуру
TMsg (сообщения Windows); h – указатель на оконный элемент управления;
wMsgFilterMin и wMsgFilterMax указывают соответственно значения первого
и последнего сообщения в очереди сообщений; wRemoveMsg определяет, удалять (PM_REMOVE) или не удалять (PM_NOREMOVE) сообщения из очереди после их
обработки функцией PeekMessage().
Владельцы мыши со скроллингом наверняка получат удовольствие от трех
обработчиков событий, появившихся в Delphi 5. При вращении колесика
мыши над элементом управления вызывается обработчик события:
property OnMouseWheel : TMouseWheelEvent;
type TMouseWheelEvent = procedure(Sender: TObject; Shift: TShiftState; WheelDelta:
Integer; MousePos: TPoint; var Handled: Boolean) of object;

Обработка событий в классах TControl и TWinControl

139

Параметры и переменные обработчика события приведены в табл. 6.7.
Таблица 6.7. Параметры типизированной процедуры TMouseWheelEvent
Параметр

Возможные значения Описание

Sender

Ссылка на объект

Элемент управления – источник сообщения.

Shift

ssShift

Нажата клавиша Shift.

ssAlt

Нажата клавиша Alt.

ssCtrl

Нажата клавиша Ctrl.

ssLeft

Нажата левая кнопка мыши.

ssRight

Нажата правая кнопка мыши.

ssDouble

Двойной щелчок любой кнопкой.

WheelDelta

integer

Отрицательное значение – вращение вниз, положительное – вверх.

MousePos

TPoint

Местоположение курсора мыши.

Handled

Boolean

True – обработка завершена, в противном случае
система может продолжить обработку.

Если требуется различать направление вращения колеса, то воспользуйтесь
обработчиками событий:
property OnMouseWheelDown: TMouseWheelUpDownEvent;
property OnMouseWheelUp: TMouseWheelUpDownEvent;
type TMouseWheelUpDownEvent = procedure(Sender: TObject; Shift: TShiftState;
MousePos: TPoint; var Handled: Boolean) of object;

где Down – прокрутка вниз, Up – вверх. Параметры обработчиков событий аналогичны параметрам OnMouseWheel (см. табл. 6.7).

События клавиатуры и класс TWinControl
Различают три события, возникающие при нажатии клавиши клавиатуры:
Таблица 6.8. Основные события клавиатуры
События

Описание

OnKeyDown()

Возникает в момент опускания клавиши.

OnKeyPress()

После ввода с клавиатуры символа ASCII.

OnKeyUp()

Вызывается после подъема клавиши.

События опускания и подъема клавиши:
property OnKeyDown : TKeyEvent;
property OnKeyUp : TKeyEvent;
TKeyEvent = procedure (Sender: TObject; var Key: Word; Shift: TShiftState) of object;

где Sender – источник сообщения; Shift – индикатор состояния клавиш Shift,
Ctrl и Alt; Key – код клавиши (табл. 6.9).

140

Глава 6. Невидимые классы

Таблица 6.9. Коды виртуальных клавиш
Код клавиши

Клавиатура IBM

Код клавиши

Клавиатура IBM

VK_CANCEL

Ctrl-Break

VK_PRIOR

Page Up

VK_BACK

Backspace

VK_NEXT

Page Down

VK_TAB

Tab

VK_END

End

VK_RETURN

Enter

VK_HOME

Home

VK_SHIFT

Shift

VK_LEFT

Стрелка влево

VK_CONTROL

Ctrl

VK_UP

Стрелка вверх

VK_MENU

Alt

VK_RIGHT

Стрелка вправо

VK_PAUSE

Pause

VK_DOWN

Стрелка вниз

VK_CAPITAL

Caps Lock

VK_INSERT

Insert

VK_ESCAPE

Esc

VK_DELETE

Delete

VK_SPACE

Пробел

VK_NUMLOCK

Num Lock

VK_SNAPSHOT

Print Screen

VK_SCROLL

Scroll Lock

Цифровые и символьные клавиши основной клавиатуры
VK_0..VK_9

0 .. 9

VK_A..VK_Z

A .. Z

Функциональные клавиши
VK_F1..VK_F12

F1 .. F12

Дополнительная цифровая клавиатура (Num Lock включен)
VK_NUMPAD0..VK_NUMPAD9 0 .. 9

VK_SUBTRACT



VK_MULTIPLY

*

VK_DECIMAL

.

VK_ADD

+

VK_DIVIDE

/

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
case Key of
VK_F1 :
{действие 1};
VK_F2 :
{действие 2};
VK_F3 :
{действие 3};
ord('W') : {действие при нажатии клавиши W};
end;
end;

В примере форма Form1 реагирует на нажатие функциональных клавиш F1, F2
и F3.
В момент нажатия кнопки генерируется событие:
property OnKeyPress: TKeyPressEvent;
TKeyPressEvent = procedure (Sender: TObject; var Key: Char) of object;

Обратите внимание, что в обработчике события OnKeyPress() переменная Key
имеет тип Char (символ ASCII).

Обработка событий в классах TControl и TWinControl

141

Рассмотрим пример использования этого события:
procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
if ((KEY<'0') or (KEY>'9') or (Length(Edit1.Text)>=6) ) and (KEY<>#8) then
{ #8 – ASCII-код клавиши Backspace}
begin
Key:=#0;
if (Length(Edit1.Text)>=6)=false then
ShowMessage('Допускаются только цифры!')
else
ShowMessage('Номер более 6 знаков!')
end;
end;

Событие контролирует верность ввода телефонного номера в строку ввода
Edit1. Процедура допускает ввод только цифр, причем длина номера не
должна превышать 6 символов. В противном случае ввод ошибочного символа отменяется (переменной Key присваивается значение ASCII #0) и выдается сообщение об ошибке.

Перетаскивание – drag-and-drop
Операция drag-and-drop (перетащить и отпустить) позволяет перемещать
объект в пределах формы, а при использовании функций Windows API –
и между разными приложениями. Все основные обработчики события описаны в классе TControl. Перемещение происходит между двумя объектами,
называемыми источником и приемником. Источник инициирует перетаскивание и содержит перетаскиваемый элемент (клиент). Приемник принимает
перетаскиваемый элемент. Классическим примером реализации операции
drag-and-drop служит Проводник Windows (Windows Explorer), предоставляющий услуги по копированию файлов из папки в папку при помощи операций перетаскивания.
Рассмотрим ряд свойств и методов, имеющих непосредственное отношение
к операции drag-and-drop.
property DragKind: TDragKind;
type TDragKind = (dkDrag, dkDock);

Свойство DragKind способно принимать два значения, определяемые классом
TDragKind. Если значение установлено в dkDrag, то при перетаскивании реализуется технология drag-and-drop, в противном случае – drag-and-dock (операция буксировки элементов управления).
Способ работы с технологией перетаскивания:
property DragMode: TDragMode;
type TDragMode = (dmManual, dmAutomatic);

В ситуации, когда свойство переведено в автоматический режим (dmAutomatic), за старт процесса перетаскивания несет ответственность сама Delphi
и программисту достаточно грамотно описать обработчики событий. Не-

142

Глава 6. Невидимые классы

сколько сложнее работать в режиме ручного управления (dmManual). В таком
случае программист обязан сам инициировать процесс перетаскивания вызовом метода BeginDrag():
procedure BeginDrag(Immediate: Boolean; Threshold: Integer = -1);

Метод рассчитан на то, что пользователь утопит кнопку мыши над объектом-клиентом и, удерживая эту кнопку в нажатом состоянии, «потащит»
объект. Если параметр Immediate установлен в true, процесс начинается немедленно, в противном случае – после перемещения мыши на некоторое расстояние (количество пикселов указывается в Threshold).
procedure TForm1.Label1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
if (ssLeft in Shift) then with Label1 do BeginDrag(False,5);
end;

В приведенном листинге предложен способ ручного вызова метода BeginDrag() для текстовой метки Label1. Для этого вызывается обработчик события OnMouseMove(). Операция начнется в том случае, если перетаскивание осуществляется левой кнопкой мыши.
Инициированный «вручную» процесс перетаскивания целесообразно завершать вызовом процедуры:
procedure EndDrag(Drop: Boolean);

Если параметру Drop передано значение true, то результаты перетаскивания будут приняты, в противном случае произойдет отмена.

За внешний вид курсора при операции перетаскивания отвечает свойство:
property DragCursor: TCursor;

Если компонент отказывается получать клиент drag-and-drop, то по умолчанию внешний вид курсора над ним будет соответствовать значению crNoDrop
(перечеркнутый круг), иначе сигналом готовности станет курсор crDrag.
Процесс перетаскивания последовательно сопровождается четырьмя событиями (табл. 6.10).
Таблица 6.10. Последовательность событий при перетаскивании
События

Инициатор Описание

OnStartDrag() Элементисточник

Над элементом-источником нажата кнопка мыши и начато движение с удержанием кнопки в нажатом состоянии.

OnDragOver() Элементприемник

Перетаскиваемый элемент находится над приемником.

OnDragDroup() Элементприемник

Над элементом-приемником отпущена кнопка мыши.

OnEndDrag()

Элементисточник

Событие возникает у элемента-источника и сигнализирует о завершении перетаскивания. Может применяться
для освобождения используемых ресурсов, отмены операции и т. п.

Обработка событий в классах TControl и TWinControl

143

Рассмотрим события более подробно:
property OnStartDrag: TStartDragEvent;
type TStartDragEvent = procedure (Sender: TObject; var DragObject: TDragObject)
of object;

Обработчик события OnStartDrag() описывается только при необходимости,
например для инициализации переменных, вызова дополнительных процедур и функций. Необязательный формальный параметр DragObject применяется наиболее подготовленными программистами для создания собственного экземпляра класса TDragObject, позволяющего осуществлять более тонкую
настройку операций перетаскивания.
Как мы уже говорили, событие OnDragOver возникает в то время, когда курсор
мыши с перетаскиваемым клиентом находится над элементом-получателем,
а также в момент отпускания кнопки мыши. Обработчик события уведомляет о готовности элемента-получателя принять клиент.
property OnDragOver: TDragOverEvent;
TDragOverEvent = procedure(Sender, Source: TObject; X, Y: Integer; State:
TDragState; var Accept: Boolean) of object;

Возможные значения параметров события OnDragOver() приведены в табл. 6.11.
Таблица 6.13. Параметры события OnDragOver()
Параметр Возможные значения Описание
Sender

TObject

Элемент-приемник.

Source

TObject

Элемент-источник.

X, Y

integer

Координаты курсора над элементом-приемником.

State

dsDragEnter

Курсор появился над объектом-приемником.

dsDragLeave

Курсор убран с поверхности приемника.

dsDragMove

Курсор движется над приемником.

Boolean

Результат решения относительно приема клиента:
true – готовность к приему клиента;
false – отказ от приема.

Accept

Задача программиста – информировать Delphi (через параметр Accept) о готовности или отказе приема операции drag-and-drop. В случае положительного решения на прием перетаскиваемого объекта при отпускании кнопки
мыши возникает событие:
property OnDragDrop: TDragDropEvent;
type TDragDropEvent = procedure(Sender, Source: TObject; X, Y: Integer) of object;

Независимо от готовности элемента-приемника к получению клиента в финале перетаскивания элемент-источник генерирует событие:
property OnEndDrag: TEndDragEvent;
type TEndDragEvent = procedure(Sender, Target: TObject; X, Y: Integer) of object;

144

Глава 6. Невидимые классы

В рамках обработчика события допускается освободить захваченные ранее
ресурсы или выполнить какие-то другие действия, определяемые программистом.
Настало время для примера. Начните новый проект и на его главной форме
разместите метку Label1 и две-три строки ввода (элементы надо взять со страницы палитры компонентов Standard). У всех строк ввода установите свойство DragMode в dmAutomatic. Опишите обработчики событий OnDragOver() и OnDragDrop() метки Label1, как предложено в примере:
procedure TForm1.Label1DragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
begin
Accept := Source is TEdit;
// приемлем только отправитель класса TEdit
end;
procedure TForm1.Label1DragDrop(Sender, Source: TObject; X, Y: Integer);
begin
with Source as TEdit do Label1.Caption:=text;
end;

В результате мы получили возможность перетаскивать текст из любой строки ввода в элемент Label1.

Буксировка – drag-and-dock
Операции drag-and-dock и drag-and-drop – ближайшие родственники. Разница между ними в том, что drag-and-dock осуществляет буксировку непосредственно элемента управления, а не просто каких-то данных. Буксировка осуществляется между доками – элементами, способными размещать на своей
поверхности другие компоненты (построенными на основе класса TWinControl). Примером реализации буксировки является пакет программ Microsoft®
Office. Приложения пакета позволяют перемещать панели с кнопками и меню, тем самым видоизменяя интерфейс программы на вкус пользователя.
Если мы планируем внедрить в наше приложение технологию буксировки,
то первым делом следует выбрать компоненты-доки, потенциальные носители буксируемых элементов. Признаком того, что элемент управления способен превратиться в док, служит наличие у него свойства:
property DockSite: Boolean;

При установке свойства в true элемент управления уведомляет буксируемый
объект о готовности предоставить ему «посадочную площадку». При этом
для автоматического масштабирования «посадочного» пространства переведите в true свойство:
property AutoSize: Boolean;

Став доком, элемент управления приобретает ряд обязательств по отношению
к размещенным на нем клиентам. Он должен знать их общее количество:
property DockClientCount: Integer;

Обработка событий в классах TControl и TWinControl

145

Более того, он обязан обеспечить доступ к любому из них по индексу в списке:
property DockClients[Index: Integer]: TControl;

Находясь на поверхности дока, клиент также не остается в неведении. В его
свойстве HostDockSite хранится ссылка на док.
Будьте внимательны. Если элемент управления помещен на компонент-док во время разработки приложения, это еще не означает, что данный элемент стал клиентом дока. Чтобы наверняка закрепить компонент за доком, в обработчике события создания формы надо поместить примерно такую строку:
Button1.HostDockSite:=ControlBar1;

При изучении технологии drag-and-drop мы уже столкнулись с двумя свойствами:
property DragKind: TDragKind;
type TDragKind = (dkDrag, dkDock);
property DragMode: TDragMode;
type TDragMode = (dmManual, dmAutomatic);

При организации буксировки первое свойство устанавливается в состояние
буксировки (DragKind:=dkDock), а режим – в зависимости от способа организации: ручной (dmManual) или автоматический (dmAutomatic).
Чаще всего в роли компонентов-доков применяют TControlBar (страница палитры Additional) и TCoolBar (страница Win32), а в качестве буксируемого клиента незаменима панель инструментов TToolBar. Для демонстрации технологии drag-and-dock даже нет необходимости нажимать клавиши.
Поместите на форму два компонента TCoolBar и превратите их в доки, установив свойство DockSite в состояние true. Перенесите на форму два элемента
управления TToolBar. Свойствам DragKind присвойте dkDock, а DragMode –
dmAutomatic. Вот и все «программирование»… Причем буксируемый элемент
вовсе не обязан приземляться на поверхность другого дока. Допускается отпускать кнопку мыши в любом месте. В таком случае Delphi создаст специальную форму-док, благодаря которой буксируемые компоненты превратятся в плавающую панель инструментов.
За класс создаваемой формы-дока отвечает свойство:
property FloatingDockSiteClass: TWinControlClass;

Наш проект можно сделать более интересным, но для этого надо изучить события, происходящие в период буксировки (табл. 6.12).
Таблица 6.12. События буксировки
События

Инициатор

Описание

OnStartDock()

Переносимый элемент.

Начало процесса буксировки.

OnGetSiteInfo() Потенциальные доки-по- Обмен информацией между доком и клилучатели.
ентом.
OnDockOver()

Док-получатель.

Перемещение курсора с буксируемым элементом над доком.

146

Глава 6. Невидимые классы

Таблица 6.12 (продолжение)
События

Инициатор

OnDockDrop() Док-получатель.
OnUnDock()

Док-источник.

Описание
Док принимает буксируемый элемент.
Уход буксируемого элемента из первоначального дока сразу после отпускания кнопки мыши.

OnEndDock() Переносимый элемент. Извещение о завершении буксировки.

В начале буксировки переносимый элемент инициирует обработку события:
property OnStartDock: TStartDockEvent;
type TStartDockEvent = procedure(Sender: TObject; var DragObject: TDragDockObject)
of object;

где Sender – непосредственно переносимый элемент, DragObject – временный
объект, создаваемый только на время буксировки и содержащий свойства
переносимого элемента.
Следующие три события связаны с доком-получателем:
property OnGetSiteInfo: TGetSiteInfoEvent;
type TGetSiteInfoEvent = procedure(Sender: TObject; DockClient: TControl; var
InfluenceRect: TRect; MousePos: TPoint; var CanDock: Boolean) of object;
property OnDockOver: TDockOverEvent;
type TDockOverEvent = procedure(Sender: TObject; Source: TDragDockObject; X, Y:
Integer; State: TDragState; var Accept: Boolean) of object;
property OnDockDrop: TDockDropEvent;
type TDockDropEvent = procedure(Sender: TObject; Source: TDragDockObject; X, Y:
Integer) of object;

В самом начале буксировки переносимый элемент оповещает все потенциальные доки (элементы со свойством DockSite, равным True) о своем намерении совершить посадку (ситуация чем-то напоминает самолет, приближающийся
к аэродрому). На что все потенциальные доки-аэропорты откликаются событием OnGetSiteInfo(). Параметры этого события в свою очередь напоминают
работу диспетчера аэропорта: Sender – ссылка на док (аэропорт), DockClient –
буксируемый объект (самолет), InfluenceRect – координаты посадочной площадки, MousePos – координаты указателя мыши (местонахождение самолета).
Но самое главное в следующем: в переменной CanDock док уведомляет о своей
готовности или отказе от приема клиента. В приведенном ниже примере док
CoolBar1 предоставит посадку только потомкам класса TtoolBar:
procedure TMainForm.CoolBar1GetSiteInfo(Sender: TObject; DockClient: TControl; var
InfluenceRect: TRect; MousePos: TPoint; var CanDock: Boolean);
begin
CanDock:=(DockClient is TToolBar);
end;

События OnDockOver и OnDockDrop несут функциональную нагрузку аналогичных обработчиков событий в операциях drag-and-drop. Перед расставанием
со своим чадом док-отправитель генерирует событие:

Основа графических элементов управления – класс TGraphicControl

147

property OnUnDock: TUnDockEvent;
type TUnDockEvent = procedure(Sender: TObject; Client: TControl; NewTarget:
TWinControl; var Allow: Boolean) of object;

Весьма полезное событие. В нем док проверяет, в надежные ли руки попадает его бывший клиент (параметр Client). Ссылка на будущего владельца находится в параметре NewTarget. Если старый владелец удовлетворен новым
хозяином, то переменной Allow присваивается значение false, а если нет –
true. В следующем примере обработчик события запрещает передавать клиент другому доку, если тот принадлежит классу TControlBar. В результате
операция drag-and-dock прерывается.
procedure TForm1.CoolBar1UnDock(Sender: TObject; Client: TControl; NewTarget:
TWinControl; var Allow: Boolean);
begin
if (NewTarget is TControlBar) then Allow:=false;
end;

Честь завершить процесс буксировки предоставляется переносимому элементу:
property OnEndDock: TEndDragEvent;
type TEndDragEvent = procedure(Sender, Target: TObject; X, Y: Integer) of object;

где Target – док-получатель, X и Y – экранные координаты нового места.
В Delphi предусмотрены методы, реализующие технологию drag-and-dock
и без участия мыши. Уже в классе TControl реализован метод, позволяющий
отправить элемент управления в док (NewDockSite), причем в параметре ARect
мы можем указать, в какое именно место:
procedure Dock (NewDockSite: TWinControl; ARect: TRect); dynamic;

Более «продвинутой» версией метода Dock() считается функция:
function ManualDock(NewDockSite: TWinControl; DropControl: TControl = nil;
ControlSide: TAlign = alNone): Boolean;

Здесь DropControl – необязательный параметр, который может использоваться, когда на поверхности дока имеются дополнительные объекты (например, страницы TTabSheet на блокноте TPageControl). Параметр ControlSide определяет метод выравнивания элемента на посадочной площадке. В случае
успешного завершения метод возвратит true.
Назначение следующей функции – перемещение элемента управления в район экрана, определенный в параметре ScreenPos:
function ManualFloat(ScreenPos: TRect): Boolean;

Основа графических элементов управления –
класс TGraphicControl
TGraphicControl – абстрактный класс, полученный из TControl. Потомки класса TGraphicControl не могут иметь фокус ввода, не способны служить контей-

148

Глава 6. Невидимые классы

нерами для других элементов и совершенно не желают обрабатывать события клавиатуры. У них даже нет дескриптора. Вот так лентяи! Вместе с тем
графические элементы управления в совершенстве решают задачи, связанные с выводом и обработкой графики, и при этом потребляют значительно
меньше системных ресурсов, чем их коллеги.
Изюминкой графического класса служит единственное новое свойство:
property Canvas: TCanvas;

Canvas (холст) обеспечивает доступ к низкоуровневым функциям графического
интерфейса устройства (GDI) Windows. Класс TCanvas более подробно рассмотрен в главе 10 «Графическая подсистема», а пока достаточно знать, что в TCanvas инкапсулированы функции для работы с контекстом устройства, простейшие функции рисования и вывода текста, а также функции объектов GDI.
Единственный новый метод, объявленный в TGraphicControl, отвечает за прорисовку графического элемента управления:
procedure Paint; virtual;

Прорисовку инициирует родительский контейнер, на котором располагается потомок класса TGraphicControl. Для этого дочернему элементу посылается сообщение операционной системы WM_PAINT.

Резюме
«Невидимые» классы представляют собой платформу для построения всех
визуальных и невизуальных элементов управления Delphi. Любая иерархия
наследования берет свое начало от абстрактного класса TObject. В этом классе реализованы надстройки над информацией о типе времени выполнения
и заложены обязательные для всех его потомков особые методы: конструктор и деструктор. Основу каждого компонента составляет класс TComponent.
На уровне этого класса объявлено свойство Name, предназначенное для хранения имени объекта, и введено понятие владельца объекта – Owner.
Следующим существенным шагом в недра иерархии наследования VCL является класс TControl. С этого момента вместо слова «компонент» мы имеем право применять термин «элемент управления». В классе TControl инкапсулированы свойства и методы, отвечающие за размеры, местоположение, видимость
и активность элемента управления. Кроме того, и это очень важно, в рамках
TControl впервые объявлен обширный перечень обработчиков событий.
Далее иерархия визуальных элементов управления разделяется на две ветви: ветвь оконных элементов управления, берущих свое начало от TwinControl, и ветвь графических элементов управления – потомков TGraphicControl.
К ключевым особенностям оконных элементов управления относятся: умение получать фокус ввода, наличие дескриптора окна и право выступать
в роли владельца для других элементов управления. Графические элементы
управления ничем подобным похвастаться не могут, но у них есть один
очень существенный плюс: потомки класса TGraphicControl – самые нетребовательные к системным ресурсам компоненты.

7
Списки и коллекции
Программисту довольно часто приходится сталкиваться с решением задач
обработки перечней однотипных данных. Это могут быть названия любимых книг, список товаров в магазине или номера счетов в банке. В главе 2
мы уже говорили об организации хранения однотипных данных и ввели понятие массива. Здесь же обсудим, в чем сходство и в чем различие между
массивами и нашими новыми знакомыми – списками и коллекциями.
Что касается сходства, то и массив, и списки с коллекциями – крупные специалисты по складированию в памяти компьютера одинаковых по типу данных. Организация доступа к данным (по крайней мере, на первый взгляд)
также весьма похожа: передаем индекс ячейки в массиве (списке, коллекции) и получаем соответствующий элемент. Но на этом сходство и заканчивается, дальше начинаются различия.
Массив – это всего лишь находящийся в ОЗУ компьютера упорядоченный
набор данных одинакового типа. Все операции с данными массива – запись,
чтение, сортировка, поиск и т. д. – должны быть реализованы программистом, поскольку массив не обладает собственными методами. В противовес
массивам, списки и коллекции – это полноценные объекты, хранящие данTObject
TList

TObjectList

TPersistent
TStrings

TStringList

TCollection

Рис. 7.1. Место списков и коллекций в VCL

TComponentList

150

Глава 7. Списки и коллекции

ные внутри себя и обслуживающие их посредством имеющихся в их распоряжении свойств и методов.

Набор строк – TStrings
Родоначальник списков, класс TStrings, описан в модуле Classes. Класс предназначен для организации работы с текстовыми строками и связанными
с ними объектами. Непосредственно с TStrings вы не столкнетесь, но потомки класса играют ключевую роль в таких компонентах, как TListBox, TcomboBox и TMemo, и во многих других элементах управления, умеющих обрабатывать многострочные текстовые данные. Как правило, в этих элементах набор
строк интегрирован в свойство Items или Lines.
Теперь познакомимся с основными задачами, за решение которых отвечает
класс TStrings:
• создание нового набора строк;
• загрузка и сохранение набора строк;
• манипуляции со строками в наборе;
• связывание внешнего объекта со строкой набора;
• операции со связанными объектами.
Информация о количестве строк в наборе предоставляется свойством:
property Count : Integer;

//только для чтения

Для получения доступа к строкам и объектам воспользуйтесь свойствами
Strings и Object соответственно:
property Strings[Index: Integer]: string;
property Objects[Index: Integer]: TObject;

Отсчет строк в TStrings начинается с 0 и заканчивается значением Count-1.
Например:
MyStrings.Strings[0] := 'Первая строка набора';

Класс TStrings предоставляет возможность сохранять или извлекать набор
строк в/из потока или файла.
procedure
procedure
procedure
procedure

SaveToStream(Stream: TStream); virtual;
LoadFromStream(Stream: TStream); virtual;
SaveToFile(const FileName: string); virtual;
LoadFromFile(const FileName: string); virtual;

//сохранение
//извлечение
//сохранение
//извлечение

в поток
из потока
в файл
из файла

Кроме того, предусмотрен способ выгрузки строк в размещенный в памяти
массив и чтение строки из памяти:
function GetText: PChar; virtual;
procedure SetText(Text: PChar); virtual;

Используемый в описанных операциях буфер памяти идентифицируется
указателем PChar; строки разделяются парой символов CR/LF. Самый последний символ в этом буфере нулевой.

151

Набор строк – TStrings

Для поиска строки в наборе предназначена функция:
function IndexOf(const S: string): Integer; virtual;

Метод осуществляет поиск первого появления строки в наборе. В случае успеха возвращается порядковый номер строки. В противном случае результат функции равен –1. За поиск связанного со строкой объекта отвечает
функция:
function IndexOfObject(AObject: TObject): Integer;

и возвращает номер объекта в наборе. Если объект отсутствует, результат
равен –1.
Ряд свойств и методов класса создан для работы с парами «параметр–значение». Например FontSize = 14 и Align = Left, где левая часть является параметром, а правая – значением. Такие пары часто используются в файлах инициализации (*.ini) различных приложений.
Для чтения или установки значения параметра Name используется свойство:
property Values[const Name: string]: string;

Для поиска в наборе порядкового номера пары «параметр=значение» предназначен метод:
function IndexOfName(const Name: string): Integer;

В случае отрицательного результата поиска возвращается значение, равное
–1. Остальные наиболее важные методы класса TSrings приведены в табл. 7.1.
Таблица 7.1. Ключевые методы класса TSrings
Метод

Описание

function Add(const S: string): Inte- Добавляет в конец набора строку S и возвращаger; virtual;
ет ее порядковый номер.
function AddObject(const S: string; Добавляет в конец набора строку S и ассоцииAObject: TObject): Integer; virtual; рует с данной строкой объект. Возвращает порядковый номер.
procedure
AddStrings(Strings: Добавляет в конец набора другой набор Strings.
TStrings); virtual;
procedure Append(const S: string);

Аналогичен методу Add(), за исключением того,
что не возвращает номер добавленной строки.

procedure Clear; virtual; abstract; Очищает набор от всех строк.
procedure Delete(Index:
virtual; abstract;

Integer); Удаляет из набора строку с порядковым номером Index.

function Equals(Strings: TStrings): Сравнивает строки текущего объекта со строкаBoolean;
ми Strings. Возвращает true в случае идентичности наборов строк.
procedure Exchange(Index1, Index2: Меняет местами пары строка–объект с номераInteger); virtual;
ми Index1 и Index2.

152

Глава 7. Списки и коллекции

Таблица 7.1 (продолжение)
Метод

Описание

procedure Insert(Index: Integer; Вставляет в набор строку string в место, опредеconst S: string); virtual; abstract; ленное переменной Index.
procedure InsertObject(Index: Inte- Вставляет в набор строку string и ассоциирует
ger; const S: string; AObject: TOb- с ней объект AObject. Место вставки определяется переменной Index.
ject);
procedure Move(CurIndex, NewIndex: Перемещает пару строка–объект из позиции
Integer); virtual;
CurIndex в позицию NewIndex.

Если при работе с потомками класса TStrings вы связывали со строками набора какие-либо объекты, то помните, что с уничтожением набора строк ассоциируемые
объекты не уничтожаются. За их корректное уничтожение отвечает программист.
У существенной части процедур и функций класса TStrings лишь объявлены заголовки методов, работающих со строками, а непосредственная реализация этих методов описана только у его потомков.

Список – TList
Абстрактный класс TList представляет собой массив указателей. Поскольку
указатели могут быть разнотипными, то и TList способен хранить список
ссылок на самые различные данные и объекты. Перечень решаемых классом
задач не очень велик. TList умеет:
• добавлять в список и удалять из списка объекты;
• проводить реорганизацию объектов внутри списка;
• организовывать поиск и доступ к объектам списка;
• сортировать объекты в списке.
Свойство Items представляет собой список ссылок на объекты или данные:
property Items[Index: Integer]: Pointer;

Можно получить указатель на объект, включенный в список, по его порядковому номеру. Для доступа к первому объекту присвойте переменной Index
значение 0, ко второму – 1, индекс последнего объекта равен Count-1. В массиве ссылок допускается присутствие пустых элементов; в этом случае указатель вернет неопределенный указатель nil.
Количество элементов списка (массива) хранится в свойстве:
property Count: Integer;

Размер массива указателей может быть установлен программистом:
property Capacity: Integer;

В таком случае количество элементов Count не должно превышать размера
массива. Конечно, размер массива не может быть безграничным. Первая
версия Delphi допускала не более 16 380 элементов, однако сегодня этот раз-

153

Список – TList

мер практически не ограничен. Предельный размер массива определен константой MaxListSize = Maxint div 16, что составляет примерно 134 миллиона
записей. Другими словами, максимальная размерность определяется ресурсами вашего ПК.
Свойство List предназначено для организации непосредственного доступа
к массиву указателей:
property List: PPointerList;

Основные методы класса TList представлены в табл. 7.2.
Таблица 7.2. Ключевые методы класса TList
Метод

Описание

function Add(Item: Pointer): In- Добавляет в конец списка указатель на новый элеteger;
мент Item.
Очищает список Count=Capacity=0.

procedure Clear; dynamic;
procedure
ger);

Inte- Удаляет указатель с порядковым номером Index.
Оставшиеся элементы списка смещаются на свободное место. Свойство Count уменьшается на 1.

Delete(Index:

procedure Exchange(Index1,
dex2: Integer);

In- Меняет местами элементы списка с порядковыми
номерами Index1 и Index2.

function Expand: TList;

Если Count=Capacity, увеличивает размер списка.
Если размер списка более 8 элементов, метод расширит список на 16 элементов, если размер от 5 до
8 элементов, то на 8, если менее 5 элементов – на 4
элемента.

function First: Pointer;

Возвращает указатель первого элемента в списке
Items[0].

function IndexOf(Item:
er): Integer;

Point- Возвращает порядковый номер элемента Item
в списке.

procedure Insert(Index:
ger; Item: Pointer);

Inte- Вставляет в список элемент Item. Место определяется переменной Index.

function Last: Pointer;

Возвращает указатель последнего элемента в списке Items[Count-1].

procedure Move(CurIndex, NewIn- Перемещает элемент списка из позиции CurIndex
dex: Integer);
в позицию NewIndex.
procedure Pack;

Упаковывает массив элементов. Из массива удаляются все указатели nil, остальные элементы смещаются к началу списка.

function Remove(Item: Pointer): Удаляет из списка первый элемент, равный Item.
Integer;
Возвращает индекс удаляемого элемента в списке.
procedure Sort(Compare: TList- Сортирует элементы списка.
SortCompare);
type TListSortCompare = function
(Item1, Item2: Pointer): Integer;

154

Глава 7. Списки и коллекции

В процессе добавления в список или удаления из списка объектов сами объекты
не создаются и не уничтожаются. Вместо этого создаются или уничтожаются
ссылки на объекты.

Список строк – TStringList
Класс TStringList получен в результате развития класса TStrings. В рамках
TStringList указатель на объект и соответствующая ему строка объединены
в структуру из двух полей:
type
//…
TStringItem = record
FString: string;
FObject: TObject;
end;

Указатели на такие записи хранятся в списке-массиве вида:
TStringItemList = array[0..MaxListSize] of TStringItem;

Ограничение размерности массива мы уже обсудили при анализе класса
TList.
Класс TStringList воплощает в жизнь все, что ему завещано классом TStrings,
и в дополнение ко всему способен:
• сортировать строки в списке;
• запретить повтор строк в сортируемых списках;
• реагировать на изменения в содержании списка.
В классе TStringList переопределены многие виртуальные методы TStrings
и добавлены некоторые новые. Признаком того, что строки отсортированы,
служит свойство:
property Sorted: Boolean;

При установке свойства в true автоматически выполняется процедура, которая посимвольно сравнивает и сортирует текстовые строки:
procedure Sort; virtual;

Если строки отсортированы (Sorted = true), не вызывайте метод вставки строк
Insert(). Вместо этого воспользуйтесь методом добавления строк Add() и вновь
отсортируйте строки.
var TownList : TStringList;
begin
TownList := TStringList.Create;
TownList.LoadFromFile('c:\Town.txt');
TownList.Add('Москва');
TownList.Add('Нижний Новгород');
TownList.Add('Ставрополь');

//
//
//
//
//

создание экземпляра класса
загрузка текстовых данных из файла
добавляем строку
добавляем строку
добавляем строку

155

Список объектов – класс TObjectList
TownList.Sorted := True;
TownList.SaveToFile('c:\Town.txt');
TownList.Free;
end;

// сортировка строк
// экспорт списка в файл
// уничтожение экземпляра класса

Класс TStringList способен контролировать попытки пополнения списка дубликатами строк. Вид реакции определяется свойством:
property Duplicates: TDuplicates;
type TDuplicates = (dupIgnore, dupAccept, dupError);

где dupIgnore – при совпадении строк добавление отклоняется; dupAccept –
добавление принимается; dupError – создается исключительная ситуация
EListError.
Для поиска строки S в списке строк применяется метод Find(). Если строка
обнаружена, функция возвращает true и порядковый номер найденной строки в параметре Index.
function Find(const S: string; var Index: Integer): Boolean; virtual;

Объекты, созданные из класса TStringList, наделены нетривиальной способностью – они реагируют на изменение своих данных:
property OnChanging: TNotifyEvent;
property OnChange: TNotifyEvent;

Данные события возникают всякий раз при добавлении, удалении, перемещении или изменении строк. Событие OnChanging() происходит в момент начала вышеперечисленных действий, а OnChange() – по их окончании. С этими
событиями мы еще не раз встретимся, например в компоненте TMemo.

Список объектов – класс TObjectList
Список объектов TObjectList объявлен в модуле Contnrs. Класс нацелен на обслуживание некоторого перечня абсолютно разнотипных объектов. Массив
хранящихся в списке объектов доступен благодаря свойству:
property Items[Index: Integer]: TObject; default;

Индекс первого элемента в списке равен 0, последнего – Count-1. Для быстрого доступа к первому и последнему объектам списка можно обратиться к методам:
function First: TObject;
function Last: TObject;

Обладание объектами списка
Ключевая особенность списка TObjectList состоит в том, что он может выступать в роли владельца всех хранящихся в нем элементов. Для этого надо установить в true свойство:
property OwnsObjects: Boolean;

156

Глава 7. Списки и коллекции

Вступив в права владения своими объектами, список станет относиться
к ним значительно строже. Теперь вызов метода Clear() не просто очистит
список, а уничтожит находящиеся в нем объекты. То же самое произойдет и
при вызове деструктора списка.

Создание экземпляра списка
Класс TObjectList может похвастаться двумя перегружаемыми версиями конструктора:
constructor Create; overload;
constructor Create(AOwnsObjects: Boolean); overload;

Разница между этими конструкторами в определении прав на содержащиеся в списке объекты. Экземпляр списка TObjectList, созданный первым конструктором, автоматически становится владельцем всех находящихся на
хранении объектов. Второй вариант метода Create() допускает, что список
не станет вступать в права владения своими элементами; для этого в параметр AOwnsObjects передается значение false.

Редактирование списка
Для добавления в список нового объекта aObject можно воспользоваться одним из двух методов:
function Add(aObject: TObject): Integer;
procedure Insert(Index: Integer; aObject: TObject);

В первом случае объект добавляется в конец списка и функция возвращает
его индекс, во втором – объект вставляется в позицию, указанную в параметре Index. Для изъятия элемента списка можно обратиться к функции:
function Remove(aObject: TObject): Integer;

Если список является владельцем этого объекта (OwnsObjects = true), экземпляр изымаемого объекта уничтожается. Если же столь кровавое решение
проблемы вас не устраивает, то для исключения элемента из списка лучше
воспользоваться методом:
function Extract(Item: TObject): TObject;

В этом случае объект не будет уничтожен вне зависимости от прав списка на
объект.
Не забывайте, что TObjectList – прямой наследник класса TList. Поэтому он обладает всеми методами своего предка, в частности Clear(), Delete(), Move() и
Sort().

Поиск объекта
Для поиска в списке объекта класса AClass предназначена функция:
function FindInstanceOf(AClass: TClass; AExact: Boolean = True;
AStartAt: Integer = 0): Integer;

Список компонентов – класс TComponentList

157

Если в параметр AExact передано значение false, то в поиск будут вовлечены
все потомки класса AClass, иначе необходимо точное совпадение класса. Метод начнет просмотр списка с позиции AStartAt и остановится на самом первом объекте, соответствующем критериям поиска. Его индекс и будет возвращен этой функцией.
Если объект известен и интересует лишь его местоположение в списке, то
воспользуйтесь методом:
function IndexOf(AObject: TObject): Integer;

Список компонентов – класс TComponentList
Список компонентов TComponentList описан в модуле Contnrs. Он является
прямым наследником только что рассмотренного списка объектов TObjectList. Основное отличие этих списков в том, что список компонентов специализируется на обслуживании потомков класса Tcomponent, что подтверждается его свойством:
property Items[Index: Integer]: TComponent; default;

Соответственно и все унаследованные от TObjectList методы (Add(), Extract(),
Remove() и т. д.) слегка подкорректированы для работы с экземплярами класса TComponent. Во всем остальном список компонентов повторяет своих славных предков.

Коллекция – класс TCollection
Класс TCollection дает начало большинству коллекций библиотеки визуальных компонентов Delphi. Коллекция TCollection и все ее многочисленные
потомки, с которыми мы столкнемся в ряде компонентов VCL, специализируются на хранении потомков экземпляров класса TCollectionItem – элементов коллекции. Например, элемент управления графический список TListView содержит коллекцию колонок TListColumns с элементами TListColumn,
строка состояния TStatusBar прячет свои панельки (TStatusPanel) в коллекции TStatusPanels, а описание характеристик поля универсального набора
данных TDataSet представлено элементом TFieldDef, хранящимся в коллекции TFieldDefs.
Не буду дальше утомлять вас мудреными названиями неведомых классов, инкапсулирующих в себе коллекции. Тем более, что с этими и многими другими элементами
управления мы подробно познакомимся в следующих главах. Но хочу обратить ваше
внимание на особенности присвоения имен классов элементов и их коллекций.
По принятой договоренности имя коллекции всегда повторяет имя класса элемента и завершается символом «s» (признак мн. ч. в англ. яз.). Таким образом, если
класс элемента называется TCoolBand, то класс соответствующей коллекции получит имя TCoolBands. Единственное исключение из правила – названия фундаментальных родительских классов TCollection и TCollectionItem, но это лишь подчеркивает их статус первопроходцев.

158

Глава 7. Списки и коллекции

Элемент коллекции – класс TCollectionItem
В отличие от относительно универсальных списков, коллекции специализируются на хранении элементов определенного класса. Основу элемента коллекции составляет потомок TPersistent – класс TCollectionItem. Для создания
и инициализации экземпляра TCollectionItem предназначен виртуальный
(доступный для модификации у классов-потомков) конструктор:
constructor Create(Collection: TCollection); virtual;

В единственном параметре метода необходимо указать коллекцию, в объятия которой сразу после своего рождения поступит элемент. Как правило,
для добавления в коллекцию нового элемента вместо явного обращения
к конструктору элемента используют методы самой коллекции, в частности
метод Add().
Как и у любого порядочного объекта, у элемента коллекции имеется деструктор, предназначенный для уничтожения экземпляра класса:
destructor Destroy; override;

Коллекция-владелец, помимо прямого обращения к деструктору элемента,
может воспользоваться своим собственным методом Delete().
var Collection : TCollection;
CItem
: TCollectionItem;
begin
Collection:=TCollection.Create(TCollectionItem); //создание коллекции
CItem:=TCollectionItem.Create(Collection);
//создание элемента коллекции
{операции с коллекцией и ее элементами}
CItem.Destroy;
//уничтожение экземпляра коллекции
Collection.Free;
//уничтожение коллекции
end;

При остром желании элемент TCollectionItem вправе потребовать суверенитета и вырваться из-под присмотра надоевшей ему коллекции. Для этого предназначено свойство:
property Collection: TCollection;

в котором элемент хранит ссылку на свою коллекцию.
//переподчинение элемента CItem1 другой коллекции
if CItem1.Collection=Collection1 then CItem1.Collection:= Collection2;
//выход из коллекции
CItem2.Collection:= nil;

При перемещении между коллекциями элемент-путешественник должен
помнить, что радушный прием ему обеспечит только коллекция, специализирующаяся на хранении элементов такого же типа, как и он. Каждый элемент знает свой индекс в коллекции:
property Index: Integer;

159

Коллекция – класс TCollection

Этот индекс может изменяться при перемещении элемента внутри коллекции или при переходе из коллекции в коллекцию. Поэтому элемент снабжен
дополнительным идентификатором:
property ID: Integer;

//только для чтения

Будьте внимательны! За назначение уникального идентификатора отвечает не
элемент, а коллекция, в которой он рождается. Это происходит в момент вызова
внутреннего метода коллекции InsertItem(). Если же элемент создается сам
по себе, отдельно от коллекции (что является исключением из правила):
Collection:=TCollection.Create(nil);

то его идентификатор не инициализируется и остается нулевым.

Еще один потенциальный идентификатор может находиться в свойстве:
property DisplayName: string;

В отличие от доступного только для чтения ID, свойством DisplayName вправе
распоряжаться программист, назначая подпись к элементу внутри кода программы. Но в этом случае не может быть и речи об уникальности, т. к. несколько элементов могут иметь одно и то же название.

Создание и уничтожение коллекции
Жизненный путь коллекции начинается с момента вызова ее конструктора.
В момент создания в параметре ItemClass требуется определить тип хранящихся в коллекции элементов.
constructor Create(ItemClass: TCollectionItemClass);

Эту же информацию можно получить, обратившись к свойству:
property ItemClass: TCollectionItemClass;

//только для чтения

Если коллекция входит в состав другого компонента, то ссылку на компонент-владелец возвратит метод:
function Owner: TPersistent;

Для разрушения коллекции предназначен метод:
destructor Destroy; override;

Вместе с коллекцией уничтожаются все принадлежащие ей элементы.

Доступ к элементу коллекции
Массив имеющихся во владениях коллекции элементов доступен благодаря
свойству:
property Items[Index: Integer]: TCollectionItem;

Первый элемент массива имеет нулевой индекс, последний – Count-1.

160

Глава 7. Списки и коллекции

Манипуляции с элементами коллекции
Для создания нового элемента коллекции с добавлением его в массив воспользуйтесь методом:
function Add: TCollectionItem;

Вновь созданный элемент помещается в конец массива. Другой вариант создания элемента опирается на метод:
function Insert(Index: Integer): TCollectionItem;

Новый элемент будет вставлен в позицию Index, остальные элементы массива сдвигаются.
var Collection : TCollection;
CItem
: TCollectionItem;
i : integer;
begin
Collection:=TCollection.Create(TCollectionItem);
for i:=0 to 9 do
begin
CItem:= Collection.Add;
CItem.DisplayName:='Элемент '+IntToStr(i);
end;
Collection.Free;
end;

Попытка изменить содержимое свойства DisplayName у элемента TCollectionItem
обречена на провал, т. к. внутренний виртуальный метод класса SetDisplayName()
представляет собой обычную заглушку. Но представленная в примере идея вполне
жизнеспособна у потомков TCollectionItem.

Удаление отдельного элемента из коллекции производится методом:
procedure Delete(Index: Integer);

где Index – порядковый номер элемента в коллекции. А для полного очищения коллекции от элементов воспользуйтесь процедурой:
procedure Clear;

У визуальных элементов управления процесс вставки, удаления и сортировки элементов коллекции сопряжен с несколькими операциями перерисовки
клиентской поверхности объекта. А если речь идет о десятках или сотнях элементов, то многократное обновление компонентов существенно снижает производительность компьютера. В таких случаях перед началом операций с элементами коллекции элемент управления «замораживают» вызовом метода:
procedure BeginUpdate; virtual;

Замороженный компонент перестает перерисовывать себя до тех пор, пока
не получит команду «отомри»:

Резюме

161

procedure EndUpdate; virtual;

Например:
var Clmn : TListColumn;
//элемент коллекции колонка
begin
with ListView1.Columns do
//коллекция колонок графического списка TListView
begin
BeginUpdate;
Clear;
Clmn:=Add;
//добавление колонки в коллекцию
Clmn.Caption:='Колонка 1';
//…
EndUpdate;
end;
ListView1.ViewStyle:=vsReport;
end;

Поиск элемента коллекции
Мы уже говорили, что каждый элемент TCollectionItem идентифицируется
уникальным значением, хранящимся в свойстве ID этого элемента. Идентификатор элемента используется для организации поиска требуемого элемента в коллекции. Для этого предназначена функция:
function FindItemID(ID: Integer): TCollectionItem;

Сравнение коллекций
Для сравнения двух коллекций воспользуйтесь функцией модуля Classes:
function CollectionsEqual(C1, C2: TCollection; Owner1, Owner2: TComponent): Boolean;

Функция проверяет идентичность данных в коллекциях C1 и C2 и возвращает true в случае полного соответствия.

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

8
Стандартные компоненты
По умолчанию в перечень стандартных компонентов входят элементы управления, которые находятся на странице Standard палитры компонентов Delphi.
Это различного рода кнопки, элементы управления, предназначенные для редактирования текста, списки выбора и меню. Кроме того, в главе рассматриваются ключевые дополнительные компоненты (страница Additional) и в первую очередь компоненты-сетки.

Компоненты для редактирования текста
Редкое приложение обходится без решения задач по вводу и редактированию текстовых данных. А спектр потенциальных задач обслуживания текстовой информации просто необъятен. Он начинается с банальных окон регистрации пользователей в базе данных и заканчивается грандиозными текстовыми редакторами.
Среда Delphi – просто Клондайк для разработки программ, специализирующихся на обработке текстовых данных. В список компонентов, нацеленных
на редактирование текстовых данных, входят как простейшие строки редактирования, так и элементы управления, способные создать текстовый редактор высочайшей сложности.
Но все по порядку. Компонент TEdit (строка ввода) предназначен для ввода
простейшей строки текста. Элемент управления TMaskEdit (строка ввода
с маской) решает проблему ввода текста в соответствии с установленным
форматом – маской. Элемент управления TMemo способен служить многострочным редактором текста. На вершине пирамиды располагается
TRichEdit (расширенный текстовый редактор), обладающий существенным
набором функций для форматирования текста. На основе этого компонента
опытный программист может разработать редактор, приближающийся по
некоторым функциональным возможностям к знаменитому текстовому процессору Microsoft® Word.

163

Компоненты для редактирования текста

Все перечисленные компоненты представляют собой классические оконные
элементы управления, обладающие характерными для объектов такого типа
особенностями. И еще их объединяет общий предок – абстрактный класс
TCustomEdit (рис. 8.1).

TWinControl
TCustomEdit

TEdit

TCustomMaskEdit

TMaskEdit

TCustomMemo

TMemo

TCustomRichEdit

TRichEdit

Рис. 8.1. Иерархия редакторов текста

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


выделять часть текста с возможностью редактирования только этой части;



описывать реакцию на изменения текста.

С основным свойством всех текстовых компонентов мы уже знакомы. Еще
при изучении класса TControl мы узнали о поле, специализирующемся на
хранении текстовой информации:
property Text: TCaption;
type TCaption = string;

Подавляющее большинство свойств и методов TCustomEdit предназначены
для обслуживания этого свойства. За установку регистра вводимого текста
отвечает свойство:
property CharCase: TEditCharCase;
type TEditCharCase = (ecNormal {нормальный}, ecUpperCase {ПРОПИСНОЙ},
ecLowerCase {строчный});

Запрет на редактирование текста накладывает свойство ReadOnly при установке его в true:
property ReadOnly: Boolean;

164

Глава 8. Стандартные компоненты

Установить предел на максимальное количество символов, хранящихся
в свойстве Text, поможет свойство MaxLength. По умолчанию установлено значение 0, т. е. длина строки не ограничена.
property MaxLength: Integer;

Если планируется применение потомка класса TCustomEdit для ввода конфиденциальной информации, такой как пароли и шифры, то для скрытия вводимого текста рекомендуется воспользоваться свойством:
property PasswordChar: Char;

Если значение свойства отличается от символа с кодом ноль (#0), то при отображении содержимого строки реальные символы будут подменяться символом из PasswordChar. Так же поступает Windows NT при вводе пользователем
своего пароля.
У строк редактирования, построенных на основе классов TEdit и TMaskEdit,
объявлено свойство AutoSelect, при установке которого в true элемент управления автоматически выделит весь текст при получении им фокуса ввода.
property AutoSelect: Boolean;

А следующее свойство решает, оставлять или нет подсветку выделенного
в элементе управления текста при потере им фокуса ввода:
property HideSelection: Boolean;

Значение true голосует за то, чтобы скрывать выделение, а false как всегда
против. Выделенный в элементе управления текст вы обнаружите в свойстве
SelText. Свойство доступно не только для чтения, но и для записи.
property SelText: string;

При желании выделение текста производится не только мышью или клавишами клавиатуры, но и программным способом. Для этого объявлены два
свойства, которые определяют порядковый номер первого выделяемого символа и всю длину выделяемого текста соответственно:
property SelStart: Integer;
property SelLength: Integer;

Для выделения всего текста воспользуйтесь методом SelectAll, а для удаления выделенного текста пригодится процедура ClearSelection:
procedure SelectAll;
procedure ClearSelection;

Для работы с выделенным текстом предусмотрены методы редактирования,
совместимые с форматом функций Windows API. В качестве аргументов
этих методов выступают указатели на строки PChar:
function GetSelTextBuf(Buffer: PChar; BufSize: Integer): Integer; virtual;
procedure SetSelTextBuf(Buffer: PChar);

Взаимодействие с буфером обмена Windows производится с помощью процедур:

Владелец файла:
Александр Аносов, mylex87@yandex.ru
Сообщить о нелицензионном использовании: http://www.books.ru/post_form/newform.php

Компоненты для редактирования текста
procedure CopyToClipboard;
procedure CutToClipboard;
procedure PasteFromClipboard;

165

//копировать в буфер
//вырезать в буфер
//вставить из буфера

Проверить, модифицировался ли текст компонента, можно при помощи
свойства:
property Modified : Boolean;

Если содержимое свойства Text изменялось, свойство Modified автоматически
переводится в состояние true. Контролируя Modified, мы можем предотвратить несанкционированную потерю данных. Изучите следующие строки кода:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if RichEdit1.Modified then
case Application.MessageBox('Сохранить данные?','Выберите операцию',MB_YESNOCANCEL) of
IDYES
: {действия по сохранению данных};
IDNO
: Action:=caHide; {отказ от сохранения данных}
IDCANCEL : Action:=caNone; {отмена закрытия формы}
end;
end;

Перед закрытием формы программист опрашивает свойство Modified расширенного редактора RichEdit1 (потомка класса TCustomEdit). Если текст компонента был модифицирован, пользователь получает уведомление об этом с одновременным предложением сохранить данные.
У потомков TCustomEdit предусмотрена возможность отмены последних изменений в редактируемом тексте. Если содержимое свойства Text изменялось
и элемент управления помнит его предыдущее значение, то в true устанавливается свойство:
property CanUndo: Boolean; // только для чтения

В таком случае откат обеспечит метод:
procedure Undo;

Полную очистку текста осуществляет метод:
procedure Clear; virtual;

Текстовые файлы DOS и Windows используют различную кодировку символов
(соответственно OEM и ANSI или Unicode). Работая в Win32, при выводе на экран файла в кодировке, принятой в MS-DOS, мы получим текст в нечитаемом
представлении. Чтобы избежать подобной проблемы, используйте свойство:
property OEMConvert: Boolean;

При изменении кодировки символов не забывайте о наличии у класса шрифтов
(TFont) свойства CharSet, определяющего номер набора символов шрифта. Для вывода кириллицы используйте RUSSIAN_CHARSET.

При каждом изменении текста вызывается обработчик события:
property OnChange: TNotifyEvent;

166

Глава 8. Стандартные компоненты

Автоматическую подстройку вертикального размера элемента управления
под высоту используемого шрифта осуществляет свойство:
property AutoSize: Boolean;

Строка ввода – класс TEdit
Класс TEdit инкапсулирует стандартный элемент управления Windows.
Компонент почти на 100% состоит из свойств и методов, ранее опубликованных у его предка TCustomEdit.
Единственное, на чем следует еще раз заострить внимание, – событие OnChange(). Ключевое событие строки ввода очень часто используется с целью организации взаимодействия текстового элемента с другими элементами управления или с функциями Windows API.
procedure TForm1.Edit1Change(Sender: TObject);
begin
Button1.Enabled:=(Edit1.text<>’’);
end;

Листинг демонстрирует способ включения и отключения кнопки при изменении содержимого строки ввода. Этот пример может пригодиться при разработке диалоговых окон, требующих обязательного ввода какой-то информации, например пароля. И пока строка ввода пуста, кнопка OK диалогового
окна остается недоступной.
Не используйте событие строки ввода OnChange() для изменения собственного
свойства Text. В этом случае высока вероятность зацикливания процедуры обработки события.

Строка ввода с маской – TMaskEdit
Строка ввода с маской служит специальным редактором, осуществляющим
контроль вводимого текста в соответствии с заданным шаблоном. Шаблон
может представлять собой номер телефона, дату, время, счет в банке. Шаблон ввода задается в редакторе маски:
property EditMask: TEditMask;

Структурно маска состоит из трех полей, разделенных точкой с запятой
(рис. 8.2). Обязательным для заполнения является только первое поле, в котором записывается непосредственно маска ввода. Допустимые символы
маски приведены в табл. 8.1. Особый интерес представляет символ наклонной черты «\», назначение которого – оповестить Delphi о том, что следующий за ним символ является литералом (символом оформления, отображаемым в строке ввода). В примере наклонная черта предшествует круглым
скобкам, в которые заключается телефонный код города, т. е. круглые скобки являются литералами.
Второе (необязательное) поле маски способно принимать два значения: 0
или 1. В первом случае в свойство строки ввода Text символы-литералы не

167

Компоненты для редактирования текста

МАСКА ВВОДА
EditMask:=!\(999\)000-0000 ; 1 ; #
EditText:=(095)111-2222

0 Text:=0951112222
1 Text:=(095)111-2222

Рис. 8.2. Строка ввода с маской

включаются, во втором в обработанный текст войдут все символы. Независимо от состояния второго поля шаблона в следующем свойстве компонента
окажется полный вариант текста:
property EditText: string;

И наконец, третье поле маски включает единственный символ, называемый
символом подстановки. До тех пор пока пользователь не введет весь текст,
этот символ будет отображаться во всех пустых полях строки ввода.
Таблица 8.1. Символы маски
Символ Описание

Символ Описание

!

Подавление пробелов в тексте

>

Перевод в верхний регистр

<

Перевод в нижний регистр

<>

Отмена перевода регистров

\

Следующий символ является ли- _
тералом

Пустое поле

L

Обязательна буква

l

Может быть буква

A

Обязательна буква или цифра

a

Может быть буква или цифра

C

Обязателен любой символ

c

Может быть любой символ

0

Обязательна цифра

9

Может быть цифра

#

Может быть цифра, знак «+» или :
«–»

Разделитель часов, минут и секунд

/

Разделитель дней, месяцев и годов ;

Разделитель полей в маске

Соответствие введенного текста маске ввода проверяет метод:
procedure ValidateEdit; virtual;

При ошибке ввода методом ValidateError() генерируется исключительная
ситуация EDBEditError. Как правило, нет необходимости использовать эту

168

Глава 8. Стандартные компоненты

процедуру в исходном коде, т. к. она автоматически вызывается Delphi, когда строка теряет фокус.
Количество символов в свойстве Text возвратит функция:
function GetTextLen: Integer;

Многострочный текстовый редактор – компонент TMemo
Создаваемые на базе класса TMemo элементы управления применяются для
ввода и редактирования многострочного неформатированного текста. Многострочный редактор представляет собой симбиоз оконного элемента управления и класса TStrings, инкапсулированного в качестве свойства:
property Lines: TStrings;

Свойство Lines позволяет хранить многострочные текстовые данные. Возможности класса TStrings, осуществляющего операции с набором строк, подробно изложены в главе 7. Взяв на вооружение эти методы, текстовый компонент TMemo может стать превосходной основой для создания простейших
приложений типа стандартного Блокнота Windows.
procedure TForm1.FormCreate(Sender: TObject);
begin
Memo1.Lines.Clear;
Memo1.Lines.LoadFromFile('C:\Autoexec.bat');
Memo1.Lines.Add('Файл загружен '+DateTimeToStr(Now));
Memo1.ReadOnly:=True;
end;

Полезное свойство по определению позиции курсора внутри редактируемого
текста возвращает номер строки и порядковый номер символа в строке:
property CaretPos: TPoint;

//только для чтения

Утверждая, что строки внутри окна ввода не поддаются форматированию,
мы несколько принизили достоинства класса TMemo. В действительности помимо настройки шрифта компонента есть еще одно свойство, позволяющее
выравнивать строки внутри окна влево, вправо или по центру:
property Alignment: TAlignment;
type TAlignment = (taLeftJustify, taRightJustify, taCenter);

Если все строки не помещаются в видимой части окна, включите линии прокрутки:
property ScrollBars: TScrollStyle;
type TScrollStyle = (ssNone, ssHorizontal, ssVertical, ssBoth);

Настройка поведения элемента управления в зависимости от длины строки
производится при помощи свойства WordWrap. Если свойство установлено
в true и длина строки превышает горизонтальный размер видимой части окна, то осуществляется мягкий перенос символов на новую строку.
property WordWrap: Boolean;

Реакция на нажатие клавиш ввода и табуляции определяется свойствами:

169

Компоненты для редактирования текста
property WantReturns : Boolean;
property WantTabs : Boolean;

Установленное в true свойство WantReturns разрешает ввод символа перевода
строки. Переведя в true свойство WantTabs, мы дадим разрешение на ввод
в текстовые данные символа табуляции.

Редактор расширенного текстового формата –
компонент TRichEdit
Компонент TRichEdit незаменим при разработке текстовых редакторов. Изначально элемент управления поддерживает возможности расширенного
текстового формата (rich-text format, RTF). А уже в наших руках все остальное, вплоть до создания собственных текстовых форматов. Большая часть
методов и свойств класса унаследована от TCustomEdit и TCustomMemo, поэтому
остановимся только на ключевых особенностях, внесенных классом TCustomRichEdit. Поведение элемента управления TRichEdit задается свойством:
property PlainText: Boolean;

Если свойство установлено в true, текст в редакторе интерпретируется как
обычный неформатированный, в противном случае вы получите все преимущества расширенного текстового формата.
Ключевое достоинство элемента управления – умение форматировать отдельный абзац текста: размещать и выравнивать текст, оформлять маркированные списки и устанавливать метки табуляции. Все эти и некоторые другие возможности определяются свойством:
property Paragraph: TParaAttributes;

//только для чтения

Возможности по установке и чтению параметров абзаца реализованы во внутреннем объекте расширенного текстового редактора – TParaAttributes. Важно помнить, что TRichEdit владеет одним-единственным объектом TParaAttributes, поэтому свойство Paragraph указывает лишь на текущий абзац (абзац, содержащий курсор). Для форматирования других абзацев необходимо
осуществить их перебор. Рассмотрим подробнее сердце компонента TRichEdit –
класс TParaAttributes.

Форматирование абзаца – класс TParaAttributes
Класс TParaAttributes определяет основные свойства абзаца текста. За выравнивание текста в абзаце отвечает свойство:
property Alignment: TAlignment;
type TAlignment = (taLeftJustify, taRightJustify, taCenter);

Предусмотрена возможность задания отступа текста: три свойства отвечают
за установку красной строки, левой и правой границ абзаца. Расстояние измеряется в пикселах.
property FirstIndent: Longint;
property LeftIndent: Longint;
property RightIndent: Longint;

//отступ первой строки абзаца
//левая граница абзаца
//правая граница абзаца

170

Глава 8. Стандартные компоненты

Например:
RichEdit1.Paragraph.FirstIndent :=50;

Форматирование текста в виде маркированного списка обеспечивается установкой в nsBullet свойства:
property Numbering: TNumberingStyle;
type TNumberingStyle = (nsNone, nsBullet);

Например:
RichEdit1.Paragraph.Numbering := nsBullet;

Позиции табуляции внутри абзаца устанавливаются в массиве Tab. Помните, что массив не вставляет непосредственно символы табуляции (это осуществляется нажатием клавиши Tab), а просто расставляет места предполагаемых табуляторов.
property Tab[Index: Byte]: Longint;

Количество табуляторов в абзаце доступно в свойстве:
property TabCount: Integer;

Класс TParaAttributes не богат собственными методами. Отметим наличие
метода «Формат по образцу» (в терминах текстового процессора Microsoft
Word). Процедура назначает абзацу такие же параметры, как и у источника
Source.
procedure Assign(Source: TPersistent); override;

Форматирование текста – класс TTextAttributes
В отличие от компонента TMemo, расширенный текстовый редактор способен
настраивать текстовые атрибуты отдельного слова и даже символа в абзаце
с помощью класса TTextAttributes. Класс выполняет комплекс настроек текстовых атрибутов с возможностью последующего их применения к выделенному тексту. Свойства класса текстовых атрибутов созвучны свойствам класса
шрифтов TFont. На базе класса TTextAttributes функционируют два свойства:
property DefAttributes: TTextAttributes;
property SelAttributes: TTextAttributes;

Первое свойство (атрибуты по умолчанию) доступно только в период выполнения приложения и описывает характеристики шрифта, устанавливаемые
по умолчанию для вновь вводимого текста. Второе свойство (атрибуты выделенного текста) возвращает или устанавливает атрибуты выделенного текста или части текста, в данный момент содержащей курсор.
public
function CurrText : TTextAttributes;
//назначение атрибутов тексту
procedure SetFontSize(NewSize : byte); //установка высоты шрифта
end;
. . .
function TfrmMain.CurrText : TTextAttributes;
begin

Компоненты для редактирования текста

171

//функция устанавливает новые атрибуты для выделенного текста
if RichEdit1.SelLength > 0
// если есть выделенный текст
then Result := RichEdit1.SelAttributes
else Result := RichEdit1.DefAttributes;
//если текст не выделен, устанавливаются атрибуты по умолчанию
end;
procedure TfrmMain.SetFontSize(NewSize : byte);
begin
//в параметре NewSize передается новая высота шрифта
CurrText.Size := NewSize;
end;

Среди множества методов класса TRichEdit особое внимание стоит уделить
встроенной функции поиска фрагмента текста:
type
TSearchType = (stWholeWord, stMatchCase);
TSearchTypes = set of TSearchType;
function FindText(const SearchStr: string; StartPos, Length: Integer;
Options: TSearchTypes): Integer;

В качестве параметров передаются: SearchStr – искомый фрагмент текста;
StartPos – место, с которого начинается поиск в тексте; StartPos + Length – место, до которого производится поиск. Опции поиска настраиваются в параметре Options, где флаг stMatchCase указывает, что поиск ведется с учетом регистра символов, а флаг stWholeWord – что при поиске учитываются только целые слова (другими словами, если вы ищете текст «метр», то поиск не будет
остановлен на слове «параметр»). В случае успеха метод возвращает позицию
первого символа найденного фрагмента в тексте, иначе результатом будет –1.

События компонента TRichEdit
Компонент обладает рядом специфичных обработчиков событий. Например,
при изменениях в области выделенного текста вызывается событие:
property OnSelectionChange: TNotifyEvent;

В компоненте предусмотрена реакция на попытку изменения защищенного
текста (текста с атрибутом Protected, установленным в True):
property OnProtectChange: TRichEditProtectChange;
type TRichEditProtectChange = procedure(Sender: TObject; StartPos, EndPos: Integer;
var AllowChange: Boolean) of object;

Параметры StartPos и EndPos проинформируют нас об участке текста, в котором была предпринята попытка редактирования. Разрешение (true) или запрет (false) на изменения определяется программистом в формальном параметре AllowChange.
При наступлении противоречий между размерами редактора TRichEdit
и объемом содержащегося в нем текста возникает событие:
property OnResizeRequest: TRichEditResizeEvent;
type TRichEditResizeEvent = procedure(Sender: TObject; Rect: TRect) of object;

172

Глава 8. Стандартные компоненты

Метод сообщает оптимальный размер редактора в параметре Rect. При уничтожении экземпляра редактора TRichEdit последний проверяет, не находится ли в буфере обмена Windows его текст. Если да, то возникает событие:
property OnSaveClipboard: TRichEditSaveClipboard;
type TRichEditSaveClipboard = procedure(Sender: TObject; NumObjects, NumChars:
Integer; var SaveClipboard: Boolean) of object;

Параметр NumObjects содержит количество объектов буфера, а NumChars – количество символов. Для очистки буфера передайте в параметр SaveClipboard
значение False.

Кнопки
Вы считали, сколько раз за день нам приходится нажимать различные кнопки? Если это кнопки пульта дистанционного управления или выключатель
настольной лампы, то при их нажатии происходит некое событие: переключается канал TV, загорается свет. Перекочевав из повседневной жизни на
экраны компьютеров, кнопка превратилась в самый популярный элемент
интерфейса современного приложения. Именно благодаря кнопке формируется интуитивно понятный даже начинающему пользователю способ управления программой.
Список кнопок не исчерпывается классом TButton, это только начало. Стремление к удобству, наглядности восприятия, повышению функциональной нагрузки на компонент породило широкий спектр элементов управления, внешне лишь отдаленно напоминающих обычную кнопку, однако очень схожих по
характеру решаемых ими задач. «Нажми кнопку – получишь результат».
Все изучаемые сегодня кнопки (за исключением TSpeedButton) берут начало от
базового класса TWinControl, подробно рассмотренного в главе 6 «Невидимые
классы», и наследуют объявленные в нем свойства и методы. Вместе с тем иерархическое дерево (рис. 8.3) «кнопкоподобных» элементов управления
представлено несколькими самостоятельными ветвями, вносящими в компоненты свои зачастую принципиальные особенности. Поэтому далее будут
рассмотрены только характерные для каждого элемента управления свойства, методы и обработчики событий. А из пройденного ранее напомним главное: основным событием, связанным с любой из кнопок, является щелчок:
property OnClick: TNotifyEvent;

Событие может быть инициировано щелчком кнопки мыши, нажатием клавиш клавиатуры или вызвано программным образом.

Кнопка – TButton
TButton – самая обычная кнопка, родившаяся вместе с первыми версиями
Windows и до сего дня не утратившая твердо занимаемых позиций. Единственная особенность кнопки проявляется при работе с модальными окнами.
Задача такого окна – ожидание выбора пользователем какого-то варианта

173

Кнопки

TControl
TWinControl
TButtonControl
TButton

TBitBtn

TCustomCheckBox

TCheckBox

TRadioButton
TCustomUpDown

TUpDown

TCustomControl

TCustomGroupBox

TGraphicControl

TCustomRadioGroup

TRadioGroup

TSpeedButton

Рис. 8.3. Иерархия элементов-кнопок

дальнейших действий приложения. Простейшим примером служит диалог
подтверждения удаления файла Проводника Windows. Проводник не продолжит выполнение поставленной задачи, пока пользователь не примет решение (нажмет кнопку удаления файла или кнопку отмены), при этом переключение к другим окнам этого приложения будет заблокировано.
В классе TButton опубликовано три новых свойства, определяющих реакцию
кнопки при работе с модальными окнами. Нажатие кнопки передает модальному окну (диалогу) значение, определенное в ее свойстве:
property ModalResult: TModalResult;
type TModalResult = Low(Integer)..High(Integer);

Это значение называется модальным результатом. Подробнее о модальных
окнах (в терминах Delphi – модальных формах) мы поговорим в главах 9 и 14,
посвященных форме и диалоговым окнам соответственно. Хорошим правилом считается размещение на модальной форме двух кнопок, реагирующих
на нажатие клавиш Enter и Esc. При нажатии Enter принимается вариант действий, по умолчанию предлагаемый модальной формой. При нажатии клавиши
отмены вариант отклоняется. Для этого предназначены два свойства:
property Default: Boolean;
property Cancel: Boolean;

При установке Default = true кнопка реагирует на нажатие клавиши Enter,
а при установке Cancel = true – на нажатие клавиши Esc.

174

Глава 8. Стандартные компоненты

Кнопка с рисунком – TBitBtn
Кнопка TBitBtn – большая модница; ее украшает битовый образ (пиктограмма). В классе TBitBtn объявлено примечательное свойство, отвечающее за
вид и модальный результат кнопки:
property Kind: TBitBtnKind;
type TBitBtnKind = (bkCustom, bkOK, bkCancel, bkHelp, bkYes, bkNo, bkClose, bkAbort,
bkRetry, bkIgnore, bkAll);

Каждому возможному значению TBitBtnKind поставлен в соответствие модальный результат, передаваемый окну: bkCustom – 0, bkOk – mrOk, … , bkAll –
mrAll. Для большей наглядности (рис. 8.4) кнопка одновременно снабжается
пиктограммой. Если вас не устраивает назначаемое по умолчанию изображение, воспользуйтесь свойством:
property Glyph: TBitmap;

Найдите подходящий файл-картинку (*.bmp),
и кнопка отобразит новую пиктограмму.
Имейте в виду, что кнопка одновременно
может поддерживать до четырех изображений, каждое из которых соответствует определенному состоянию (кнопка не нажата, не
активна, в момент щелчка, нажата). Количество подключаемых пиктограмм определяется свойством:

Рис. 8.4. Свойство кнопки Kind

property NumGlyphs: TNumGlyphs;
type TNumGlyphs = 1..4;

Все пиктограммы должны быть объединены в единый файл. Посмотрите набор
файлов *.bmp, предоставленных Delphi. По умолчанию он размещается в папке:
…\Program Files\Common Files\Borland Shared\Images\Buttons

Каждый файл каталога включает два рисунка (NumGlyphs=2) для отображения
обычного и неактивного состояния кнопки.

По желанию разработчика местоположение пиктограммы на рабочей поверхности кнопки изменяется при помощи свойства:
property Layout: TButtonLayout;
type TButtonLayout = (blGlyphLeft, blGlyphRight, blGlyphTop, blGlyphBottom);

По умолчанию пиктограмма расположена слева от заголовка кнопки (Layout
= blGlyphLeft). Расстояние (в пикселах) от края кнопки до границы пиктограммы определяется свойством:
property Margin: Integer;

Расстояние между пиктограммой и текстом заголовка кнопки изменяется
в свойстве:
property Spacing: Integer;

175

Кнопки

За стиль кнопки отвечает свойство:
property Style: TButtonStyle;
type TButtonStyle = (bsAutoDetect, bsWin31, bsNew);

Это не что иное, как наследие времен перехода с ОС Windows 3.1 на ОС
Win32. Свойство предназначено для обеспечения обратной совместимости
проектов различных версий Delphi.

Переключатель – TRadioButton
Элемент управления TRadioButton никогда не применяется сам по себе, ему всегда требуется хотя бы один напарник. Особенность группы переключателей
в том, что одновременно может быть выбрана только одна кнопка. То же самое происходит в радиоприемнике: при нажатии кнопки УКВ утопленная ранее кнопка диапазона КВ автоматически возвращается в исходное положение.
Для объединения переключателей в группу их размещают на панели (TPanel) или на
панели группировки (TGroupBox).

За состояние кнопки отвечает свойство:
property Checked: Boolean;

Если кнопка выбрана, свойство возвратит значение true, если нет – false. Состояние кнопки можно изменять программным образом:
RadioButton1.Checked:=true;

Следующее свойство определяет, с какой стороны от кнопки будет располагаться поясняющая надпись из свойства Caption:
property Alignment: TLeftRight;
type TLeftRight = taLeftJustify..taRightJustify;

Группа переключателей – TRadioGroup
Группа переключателей – это комбинация панели группировки и переключателей, представленная единым элементом управления. После размещения
компонента TRadioGroup на форме он напоминает обычную пустую панель
группировки TGroupBox, т. к. не содержит ни одного переключателя. Однако
палитра компонентов не потребуется для того, чтобы группа переключателей стала владельцем кнопок. Все гораздо проще. В классе TRadioGroup реализовано специальное свойство-список, в которое следует внести заголовки
переключателей по принципу: одна строка – один переключатель:
property Items: TStrings;

По умолчанию список пуст. Во время проектирования добавление или удаление кнопок осуществляется в специальном редакторе. Во время выполнения программы настройка списка осуществляется в соответствии с правилами работы класса TStrings, рассмотренного в главе 7 «Списки и коллекции».

176

Глава 8. Стандартные компоненты

with RadioGroup1.Items do
begin
Add('Кнопка 1'); // добавили в конец списка новую кнопку
Add('Кнопка 2');
Delete(0);
// удалили первый элемент списка
end;

Для идентификации выбранной кнопки предназначено свойство:
property ItemIndex: Integer;

Это свойство возвращает порядковый номер выбранной кнопки в списке items.
Если выбрана первая кнопка, то ее индекс равен 0, следующая кнопка – 1,
последняя – Count-1. Если ни одна из кнопок не выбрана, свойство возвратит
значение –1.
procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
case RadioGroup1.ItemIndex of
0 : <операция 1>;
1 : <операция 2>;
end;
end;

Если вам не по душе работа с безликими индексами, попробуйте обратиться
к заголовкам переключателей, но код при этом несколько усложнится:
if RadioGroup1.Items[RadioGroup1.ItemIndex] = 'Выход' then Form1.Close;

Группа переключателей позволяет размещать кнопки в несколько колонок.
При этом надо помнить об ограничении: количество колонок не должно превышать 16. Число колонок определяется свойством:
property Columns: Integer;

Флажок – TCheckBox
Элемент управления TCheckBox используется в тех случаях, когда программе
требуется узнать мнение пользователя по тому или иному вопросу. Откройте
Delphi и выберите меню Project → Options… Вкладка Compiler (рис. 8.5) буквально усеяна флажками. Подчеркну, в своем выборе пользователь должен
быть предельно лаконичен: от него ждут ответ «Да» (компонент отмечен
флажком) или «Нет» (флажок снят). Текущее состояние компонента оценивается свойством:
property State: TCheckBoxState;
type TCheckBoxState = (cbUnchecked, cbChecked, cbGrayed);

где cbUnchecked – флажок не установлен, cbChecked – флажок установлен и cbGrayed – промежуточное состояние (серое поле). Программист может запретить использовать третье неопределенное состояние, для чего нужно установить в false свойство:
property AllowGrayed: Boolean;

Владелец файла:
Александр Аносов, mylex87@yandex.ru
Сообщить о нелицензионном использовании: http://www.books.ru/post_form/newform.php

Кнопки

177

Рис. 8.5. Страница настройки компилятора –
практический пример применения флажков

Если кнопка выбрана (State = cbChecked), свойство Checked принимает значение, равное true, в остальных случаях (State = cbUnchecked или cbGrayed) значение равно false.
property Checked: Boolean;

Кнопки изменения значения – TUpDown
TUpDown – элемент управления в виде двух объединенных кнопок. Основная
задача – пошаговое изменение значения целого числа. Текущее значение
описывается свойством:
property Position: SmallInt;

При щелчке по соответствующей кнопке значение свойства увеличивается
или уменьшается с шагом, определенным в свойстве:
property Increment: Integer;

По умолчанию величина инкремента установлена в 1. Диапазон значений,
которые может принимать свойство Position, ограничивается максимальным и минимальным значениями:
property Max: SmallInt;
property Min: SmallInt;

При желании разрешить смену значения Position в цикле установите в true
свойство Wrap. Тогда при достижении максимального значения следующий
щелчок по кнопке приращения присваивает свойству Position значение
из свойства Min. И наоборот.
property Wrap: Boolean;

178

Глава 8. Стандартные компоненты

Находясь в фокусе ввода, компонент TUpDown получит возможность реагировать на клавиши управления курсором (клавиши со стрелками), если установить в true свойство:
property ArrowKeys: Boolean;

У элемента управления TUpDown нет своего заголовка, но он способен отображать текущее значение Position в ассоциированном с ним объекте, связь
с которым определяется свойством:
property Associate: TWinControl;

В роли связанного объекта может выступать любой наследник класса TWinControl, но обычно применяется строка ввода (компонент TEdit) или метка
(компонент TLabel).
Для повышения наглядности предусмотрена возможность разделения тысяч
при отображении свойства Position в ассоциированном с элементом управления объекте. Для этого присвойте значение true свойству:
property Thousands : Boolean;

Расположение компонента TUpDown слева или справа от связанного объекта
определяется в свойстве:
property AlignButton: TUDAlignButton;
type TUDAlignButton = (udLeft, udRight);

Кнопки элемента управления TUpDown могут быть сориентированы по горизонтали или вертикали:
property Orientation: TUDOrientation;
type TUDOrientation = (udHorizontal, udVertical);

Быстрая кнопка – TSpeedButton
Из всего списка кнопок элемент TSpeedButton – единственный неоконный элемент управления. Кнопка построена на основе графического класса TGraphicControl (подробнее класс рассмотрен в главе 6 «Невидимые классы»). Отсутствие быстрой кнопки в иерархическом списке наследования класса TWinControl лишило ее возможности реагировать на события клавиатуры и получать
фокус ввода, но взамен значительно снизило потребность компонента в системных ресурсах.
Быстрая кнопка, как правило, применяется в составе панелей инструментов. По аналогии с классом TBitBtn кнопка TSpeedButton умеет отображать на
своей поверхности пиктограмму, которая хранится в свойстве:
property Glyph: TBitmap;

В дополнение к этому быстрая кнопка способна отказаться от объемного
внешнего вида и стать абсолютно плоской, но в момент подвода курсора «вынырнуть» на поверхность. Для задания такого поведения следует установить в true свойство:
property Flat: Boolean;

Элементы управления – списки

179

Кнопку можно сделать прозрачной (Transparent = true), воспользовавшись
свойством:
property Transparent: Boolean;

Несколько быстрых кнопок обычно объединяют в группу (рис. 8.6). Для этого свойству GroupIndex всех кнопок присваивается одинаковое, отличное от
нуля значение:
property GroupIndex: Integer;

Объединение двух и более элементов управления класса TSpeedButton приводит к весьма полезному результату: все кнопки группы приобретают зависимую фиксацию (очень похожую
на поведение компонента TRadioGroup). Теперь Рис. 8.6. Группа компонентов
щелчок по любой из быстрых кнопок перево- TSpeedButton
дит последнюю в утопленное состояние и одновременно возвращает в исходное предыдущую нажатую кнопку. О состоянии
кнопки в группе (нажата или нет) можно судить по содержимому свойства:
property Down: Boolean;

В утопленном состоянии Down = true. Если кнопка не входит ни в какую из
групп (GroupIndex = 0), свойство Down неработоспособно. Для того чтобы хотя
бы одна из кнопок группы всегда находилась в утопленном состоянии, установите в false свойство:
property AllowAllUp: Boolean;

Элементы управления – списки
Списки предназначены для хранения набора текстовых строк и обеспечения
выбора одной или нескольких из них пользователем. В отличие от других элементов управления, специализирующихся на организации выбора (группа
переключателей), списки способны хранить сотни текстовых элементов и при
этом занимать минимум рабочего пространства формы. В дополнение ко всему списки предоставляют удобную возможность редактировать свое содержимое во время выполнения приложения.
Ключевым свойством всех списков является уже знакомое нам свойство, основанное на классе TStrings и подробно рассмотренное в главе 7:
property Items: TStrings;

Доступ к текстовым строкам элемента управления производится согласно
порядковому номеру (индексу) строки в списке:
function GetItemText(Index : cardinal) : string;
begin
if index <= ListBox1.Items.Count-1 do
Result:=ListBox1.Items.Strings[Index]
else Result:=’’;
end;

180

Глава 8. Стандартные компоненты

TControl
TWinControl
TCustomListBox
TListBox
TCheckListBox
TCustomComboBox

TComboBox

Рис. 8.7. Иерархия элементов–списков

Индекс отмеченного элемента списка хранится в свойстве:
property ItemIndex : Integer;

При установке в true свойства Sorted текстовые строки в списке сортируются
в алфавитном порядке:
property Sorted : Boolean;

Для быстрого удаления всех текстовых строк списка предназначен метод:
procedure Clear;

Для вставки в компонент новой строки, помимо обширного перечня методов
класса TStrings, можно воспользоваться процедурой AddItem(). Особенность
метода в том, что кроме текста Item он позволяет связать с элементом внешние данные AObject.
procedure AddItem(Item: string, AObject: Object);

Списки позволяют одновременно выбрать несколько элементов. Для этого
достаточно перевести в состояние true свойство:
property MultiSelect: Boolean;

//по умолчанию false

Если элемент с порядковым номером Index выбран, то свойство Selected возвратит значение true:
property Selected[Index: Integer]: Boolean;

Общее количество выбранных элементов можно выяснить из свойства:
property SelCount: Integer;

Для одновременного выбора всех строк списка обратимся к методу:
procedure SelectAll;

Полезной особенностью всех компонентов-списков является возможность
настройки способа прорисовки своего содержимого. По умолчанию компо-

Элементы управления – списки

181

ненты отображают только текстовые строки. Но отказавшись от стандартных методов прорисовки, программист получит возможность украсить элемент управления списки графическими изображениями, изменить начертание шрифтов, цвет и т. п. В первую очередь за определение способа прорисовки компонента отвечает его стиль (Style). Различают три стиля компонентов
TListBox и TCheckListBox:
property Style: TListBoxStyle;
type TListBoxStyle = (lbStandard, lbOwnerDrawFixed, lbOwnerDrawVariable);

Таблица 8.2. Стили списка TListBoxStyle
Стиль

Поведение элемента управления

lbStandard

Высота всех строк и режим прорисовки определяется системой.

lbOwnerDrawFixed

Программный доступ к прорисовке элементов списка. Высота
строк указывается в свойстве ItemHeight.

lbOwnerDrawVariable Программный доступ к прорисовке элементов списка. Высота
строк задается в обработчике события OnMeasureItem.

Переопределив стиль списка на lbOwnerDrawFixed или lbOwnerDrawVariable
и слегка поколдовав с исходным кодом, вполне реально дополнить список
картинками. Для этого применяется обработчик события OnDrawItem():
property OnDrawItem: TDrawItemEvent;
TDrawItemEvent = procedure(Control: TWinControl; Index: Integer; Rect: TRect;
State: TOwnerDrawState) of object;
TOwnerDrawState = set of (odSelected, odGrayed, odDisabled, odChecked, odFocused);

Это событие вызывается каждый раз, когда списку необходимо перерисовать свой элемент.
Таблица 8.3. Параметры события TDrawItemEvent
Параметр

Назначение параметра

Control: TWinControl

Элемент управления, содержащий элемент Item.

Index: Integer

Порядковый номер элемента Item в наборе Items списка.

Rect: TRect

Прямоугольник, отведенный элементу Item.

State: TOwnerDrawState Состояние элемента списка: odSelected – выбран, odDisabled –
заблокирован, odFocused – в фокусе.

Разместите на форме компонент ListBox1 и установите его свойство Style:=
lbOwnerDrawFixed. В секции private раздела interface исходного модуля объявите переменную WinDir : string, в которой будет храниться путь к каталогу Windows компьютера. Опишите метод OnShow() формы, как указано в примере:
procedure TForm1.FormShow(Sender: TObject);
var buffer:Array[0..max_path] of char;
BmpPath:string;
begin

182

Глава 8. Стандартные компоненты

GetWindowsDirectory(buffer,max_path); //узнаем каталог Windows
WinDir:=StrPas(buffer);
//передаем путь к каталогу в переменную
BmpPath:=WinDir+'\*.bmp'#0;
//создаем маску для сбора рисунков *.bmp
ListBox1.Perform(LB_DIR, DDL_READWRITE, INTEGER(@BmpPath[1]));
end;

Задачей процедуры является выяснение местонахождения каталога установки Windows и сбор путей ко всем файлам формата *.bmp в TListBox.
Для определения пути к каталогам Windows всегда применяйте функцию Win32 API
GetWindowsDirectory(). Это правило сделает программный продукт более адаптивным к компьютерам ваших клиентов. Дело в том, что не всегда инсталляция Windows проведена с параметрами по умолчанию: C:\Windows. Например, у пользователя,
работающего под Windows 2000, этот адрес соответствует строке: C:\WINNT.

Вторым этапом напишите код в обработчике события OnDrawItem():
procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer; Rect:
TRect; State: TOwnerDrawState);
var Bitmap : TBitmap;
OffSet : Integer;
ItemRect : TRect;
begin
with (control as TListBox).Canvas do
begin
FillRect(Rect);
// заливка прямоугольника белым цветом
Bitmap:=TBitmap.create;
// создание битовой карты
Bitmap.LoadFromFile(WinDir+'\'+ListBox1.Items[Index]);
ItemRect:=Bounds(Rect.Left+1,Rect.Top+1,Rect.Bottom-Rect.Top-2,
Rect.Bottom-Rect.Top-2);
StretchDraw(ItemRect, Bitmap);
//вывод изображения
Offset:=Rect.Bottom-Rect.top+6;
//расчет отступа текста
TextOut(Rect.Left+Offset,Rect.Top,ListBox1.Items[index]); //вывод текста
Bitmap.Free;
//освобождение ресурсов Bitmap
end;
end;

При использовании стиля lbOwnerDrawVariable высота каждой строки задается программистом в обработчике события:
property OnMeasureItem: TMeasureItemEvent;
type TMeasureItemEvent = procedure(Control: TWinControl;
Index: Integer; var Height: Integer) of object;

Обработчик манипулирует тремя параметрами (табл. 8.4):
Таблица 8.4. Параметры события OnMeasureItem()
Параметр

Назначение параметра

Control

Элемент управления, содержащий элемент Item.

Index

Порядковый номер строки в свойстве Items.

Height

Определение высоты строки с индексом Index.

Элементы управления – списки

183

Понятие стиль компонента TComboBox сочетает в себе не только решение вопроса графического оформления элемента управления, но и оказывает влияние на операции редактирования списка строк.
property Style: TComboBoxStyle;
type TComboBoxStyle = (csDropDown, csSimple, csDropDownList, csOwnerDrawFixed,
csOwnerDrawVariable);

Таблица 8.5. Значения TComboBoxStyle
Значение

Поведение элемента управления

csDropDown

При нажатии кнопки в правой части элемента открывается или
закрывается список. Список доступен для редактирования.

csSimple

Список компонента виден постоянно.

csDropDownList

Поведение аналогично csDropDown, но без возможности редактирования.

csOwnerDrawFixed

Программный доступ к прорисовке элементов списка. Постоянная высота элемента, определяемая в свойстве ItemHeight.

csOwnerDrawVariable Программный доступ к прорисовке элементов списка. Переменная высота элемента.

По аналогии с TListBox элемент управления TComboBox обеспечен методами
программной прорисовки своих элементов (OnDrawItem) и регулировки высоты строк (OnMeasureItem):
property OnDrawItem: TDrawItemEvent;
property OnMeasureItem: TMeasureItemEvent;

Изучив общие свойства и методы списков, рассмотрим реальные элементы
управления.

Простой список – TListBox
Простой список TListBox представляет собой многострочный элемент управления. По умолчанию одновременный выбор двух и более строк запрещен.
Однако этот запрет можно снять, установив в true свойство:
property MultiSelect: Boolean;

Если многострочный выбор разрешен, то на способ выбора строк оказывает
влияние свойство:
property ExtendedSelect: Boolean;

В состоянии true выбор осуществляется щелчком кнопки мыши при нажатой клавише Ctrl или Shift, в противном случае одновременный выбор нескольких строк производится только мышью. Общее количество выбранных
пользователем строк можно узнать из свойства:
property SelCount: Integer;

184

Глава 8. Стандартные компоненты

При запрете на одновременный выбор двух и более строк (MultiSelect = false)
возвращаемое значение всегда равно –1. Проверку факта выделения элемента с порядковым номером Index производит свойство:
property Selected[Index: Integer]: Boolean;

Если элемент выделен, свойство установится в состояние true. Данное свойство доступно только во время выполнения программы.
if ListBox1.Selected[5] then ShowMessage(‘Выбран 5 элемент’);

Можно разместить элементы списка в несколько колонок, если отказаться
от нулевого значения в свойстве:
property Columns: Integer;

Размер отступа табуляции настраивается в свойстве:
property TabWidth: Integer;

Например:
Listbox1.TabWidth:=20;
for i:=0 to 9 do Listbox1.items.Add('A'+#9+'B'+#9+'C'); //#9 – код табуляции

За автоматическую подстройку высоты элемента отвечает свойство:
property IntegralHeight: Boolean;

По умолчанию оно установлено в false и допускает «обрезание» последнего
элемента списка. Внешний вид границ элемента управления определяется
свойством:
property BorderStyle: TBorderStyle;

У наследников класса TCustomListBox опубликован метод, позволяющий определять индекс элемента списка по его координатам Pos:TPoint в клиентской части списка:
function ItemAtPos(Pos: TPoint; Existing: Boolean): Integer;

При отсутствии элемента списка в точке Pos возвращаемое значение определяется параметром Existing: true – результат функции равен –1, false – возвращается индекс последнего. Этот метод обычно работает в содружестве с
событиями, реагирующими на манипуляции мышью.
procedure TForm1.ListBox1MouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
var Pos : TPoint;
sText : string;
Index : integer;
begin
Pos.x:=X; Pos.y:=Y;
Index:=ListBox1.ItemAtPos(Pos, true);
if Index<>-1 then sText:=ListBox1.Items.Strings[Index];
end;

Элементы управления – списки

185

Для получения координат прямоугольника, содержащего элемент списка
Item, используйте метод, который может понадобиться в операциях прорисовки:
function ItemRect(Item: Integer): TRect;

Список с флажками – TCheckListBox
TCheckListBox – компонент, объединивший достоинства списка и элемента
флажок. Каждая строка списка снабжена индивидуальным переключателем со свойствами, напоминающими отдельный элемент управления TCheckBox. Флажок может быть установлен в одно из трех состояний:
property State[Index: Integer]: TCheckBoxState;
type TCheckBoxState = (cbUnchecked, cbChecked, cbGrayed);

где Index – индекс элемента в списке, cbUnchecked – флажок не установлен,
cbChecked – флажок установлен, cbGrayed – промежуточное состояние. Запретить использовать неопределенное состояние можно путем установки в false
свойства:
property AllowGrayed: Boolean;

Если в программе нет необходимости устанавливать флажок в промежуточное состояние, то вместо свойства State достаточно применять свойство:
property Checked[Index: Integer]: Boolean;

Внедрение флажков упростило процесс одновременного выбора нескольких
элементов списка и позволило не публиковать в TCheckListBox свойства класса TCustomListBox, такие как MultiSelect и ExtendedSelect. Кроме того, программист получил возможность «отключить» любой элемент списка, установив в false свойство ItemEnabled, что аналогично установке состояния
флажка State в cbGrayed.
property ItemEnabled[Index: Integer]: Boolean;

Начиная с Delphi 6 TCheckListBox приобрел еще один способ выделения строки:
property Header[Index: Integer]: Boolean;

Установив это свойство в true, вы укажете компоненту, что элемент списка
с номером Index утрачивает статус флажка и переходит в разряд заголовков
(пояснительной метки). Цвет шрифта заголовка устанавливается свойством:
property HeaderColor: TColor;

а цвет фона строки заголовка – свойством:
property HeaderBackgroundColor: TColor;

Перечень обработчиков событий повторяет события элемента TListBox и дополнен обработчиком события щелчка по флажку:
property OnClickCheck: TNotifyEvent;

186

Глава 8. Стандартные компоненты

Комбинированный список – TComboBox
Комбинированный список представляет собой симбиоз строки ввода и обычного списка, выпадающего по щелчку. Внешний вид и особенности поведения определяются в рассмотренном ранее свойстве Style. Количество строк (по умолчанию – 8), показываемых в ниспадающем списке, определяется свойством:
property DropDownCount: Integer;

Во время выполнения приложения можно проверить состояние списка (показан или нет):
property DroppedDown: Boolean;

Также при помощи этого свойства осуществляется принудительный показ
или скрытие списка. В момент открытия списка вызывается событие:
property OnDropDown: TNotifyEvent;

На свертывание списка компонент отреагирует событием:
property OnCloseUp: TNotifyEvent;

Редактируемый текст строки ввода списка доступен через свойство:
property Text: TCaption;

Возможность редактирования определяется свойством Style и подробно раскрыта в табл. 8.5. Допустимо принудительное задание регистра символов
при вводе текста – обычный, только верхний и только нижний регистры:
property CharCase: TEditCharCase;
type TEditCharCase = (ecNormal, ecUpperCase, ecLowerCase);

Во время выполнения приложения часть текста в строке ввода может быть
выделена:
property SelStart: Integer; //позиция первого выделяемого символа
property SelLength: Integer; //количество выделяемых символов
property SelText: string;
//содержит выделенный текст

Если необходимо выделить все содержимое строки ввода, используйте метод:
procedure SelectAll;

В ответ на выбор пользователем строки в выпадающем списке генерируется
событие:
property OnSelect: TNotifyEvent;

Изменение текста порождает событие:
property OnChange: TNotifyEvent;

На длину текста можно наложить ограничение при помощи свойства:
property MaxLength: Integer;

Оригинальный способ использования комбинированного списка для сбора шрифтов
системы приведен в главе 10 «Графическая подсистема» в разделе, посвященном
классу TFont.

187

Сетки

Сетки
Вы когда-нибудь сталкивались с электронными таблицами? Если да, то наверняка у вас на слуху названия таких программных продуктов, как Microsoft
Excel, Lotus 1-2-3, Quattro Pro. Все они построены на основе таблиц-сеток.
В Delphi предложено два базовых класса TDrawGrid и TStringGrid, представляющих собой сетки ячеек (рис. 8.8). Каждая ячейка однозначно идентифицируется при помощи порядковых номеров ее столбца и строки. В самом общем случае ячейки способны отображать графическую и текстовую информацию, а если проявить немного сообразительности, то с ячейкой вполне
можно связать данные любой сложности.
Компонент TDrawGrid (сетка для рисования) в первую очередь приспособлен
для вывода графической информации. Сетка не умеет самостоятельно хранить информацию, поэтому этот элемент управления применяется в тех случаях, когда данные, отображаемые в ячейке, содержатся где-то в другом
месте, например в массиве.
Компонент TStringGrid (сетка строк) построен на базе класса TDrawGrid и поэтому вобрал в себя все самое полезное, что есть в сетке для рисования. Кроме того, он способен хранить текстовые данные. По сути, TStringGrid является двумерным динамическим массивом типа String, а ячейка сетки – визуальным представлением элемента массива.

TWinControl
TCustomControl
TCustomGrid
TDrawGrid

TStringGrid

Рис. 8.8. Иерархия компонентов-сеток

Сетка для рисования – компонент TDrawGrid
Общее количество столбцов и строк задается в свойствах:
property ColCount: Longint;
property RowCount: Longint;

//число столбцов
//число строк

Часть столбцов и строк сетки можно сделать фиксированными. Фиксированный ряд (колонка) на экране компьютера отображается другим цветом.
Ячейки такого рода недоступны для редактирования пользователем, не перемещаются с помощью полос прокрутки и обычно используются для размещения заголовков и комментариев.

188
property FixedCols: Integer;
property FixedRows: Integer;

Глава 8. Стандартные компоненты
//число фиксированных колонок
//число фиксированных строк

Для того чтобы отказаться от фиксированных колонок или строк присвойте
соответствующему свойству значение 0. Еще одной особенностью фиксированного ряда (колонки) является то, что он может располагаться только
на первых позициях сетки. Другими словами, если в сетке объявлен один
фиксированный ряд, то его индекс aRow всегда равен 0. Для выделения фиксированных ячеек цветом используется свойство:
property FixedColor: TColor;

Ширина столбцов и высота строк сетки задается по умолчанию в свойствах:
property DefaultColWidth: Integer;
property DefaultRowHeight: Integer;

//ширина колонки по умолчанию
//высота строки по умолчанию

Присвоение перечисленным свойствам каких-то значений «причесывает»
все ячейки сетки под одну гребенку – они получают одинаковые размеры
высоты и ширины. Вместе с тем возможна и индивидуальная настройка размеров любой колонки или строки. Для этого надо знать их порядковый номер Index:
property ColWidths[Index: Longint]: Integer; //ширина колонки
property RowHeights[Index: Longint]: Integer; //высота строки

Эти свойства недоступны в Инспекторе объектов и изменять их можно только в коде программы. Есть возможность настроить толщину линий, разделяющих ячейки:
property GridLineWidth: Integer;

//по умолчанию 1 пиксел

Два свойства (только для чтения) хранят информацию о высоте и ширине
всей таблицы:
property GridHeight: Integer;
property GridWidth: Integer;

//высота сетки
//ширина сетки

Если в видимой области сетки все ячейки не помещаются, то по краям сетки
необходимо включить линии прокрутки:
property ScrollBars: TScrollStyle;
type TScrollStyle = (ssNone, ssHorizontal, ssVertical, ssBoth);

Количество полностью видимых в данный момент столбцов или строк можно получить из свойств:
property VisibleColCount: Integer;
property VisibleRowCount: Integer;

//видимых колонок
//видимых рядов

При прокрутке сетки зачастую необходимо выяснить номера столбца и строки, соответствующих левой верхней видимой ячейке (не находящейся в фиксированной области).
property LeftCol: Longint;
property TopRow: Longint;

//индекс левой ячейки
//индекс верхней ячейки

С изменением координат видимой ячейки непосредственно связано событие:

189

Сетки
property OnTopLeftChanged: TNotifyEvent;

Например:
procedure TForm1.DrawGrid1TopLeftChanged(Sender: TObject);
begin
Label1.Caption:=Format('Колонка %d Строка %d', [DrawGrid1.LeftCol,DrawGrid1.TopRow]);
end;

Широкий спектр услуг по настройке сетки обеспечивает множество Options,
возможные значения которого представлены в табл. 8.6.
property Options: TGridOptions;
TGridOptions = set of TGridOption;

Таблица 8.6. Опции сетки TGridOption
Значение

Результат

goFixedVertLine

Ячейки фиксированной области разделены вертикальными линиями.

goFixedHorzLine

Ячейки фиксированной области разделены горизонтальными
линиями.

goVertLine

Колонки разделяются линиями.

goHorzLine

Строки разделяются линиями.

goRangeSelect

Одновременно может быть выделено несколько ячеек (опция
не работает при включенной опции goEditing).

goDrawFocusSelected Сфокусированная ячейка выделяется цветом.
goRowSizing

Разрешение на установку индивидуальных размеров для строки, колонки.

goColSizing
goRowMoving

Разрешение на перемещение мышью строк, колонок.

goColMoving
goEditing

Разрешает пользователю редактировать текст ячеек.

goTabs

При включении переход между столбцами осуществляется при
помощи клавиши Tab и Shift+Tab.

goRowSelect

Запрещает выделение отдельной ячейки. Вместо этого выделяется весь ряд. При использовании этой опции отключается goAlwaysShowEditor.

goAlwaysShowEditor

Имеет значение при включенной опции goEditing. Если опция
включена, то выбрав ячейку, пользователь сразу оказывается
в режиме редактирования. В противном случае необходимо
предварительное нажатие клавиш Enter или F2.

goThumbTracking

Определяет порядок прорисовки содержимого ячеек при перемещении бегунка полосы прокрутки сетки. Если опция отключена,
прорисовка начинается после отпускания кнопки мыши, в противном случае прорисовка осуществляется при малейшем перемещении бегунка, а это требует дополнительного расхода ресурсов.

190

Глава 8. Стандартные компоненты

Не забывайте, что отсчет столбцов и строк начинается с нуля, т. е. верхняя левая ячейка сетки располагается в нулевом столбце и нулевой строке.

Адрес каждой ячейки сетки однозначно задается номером столбца и строки.
Для того чтобы узнать координаты выделенной ячейки, обратитесь к свойствам:
property Col: Longint;
property Row: Longint;

На этом способы выяснения координат ячейки не заканчиваются. Экранные
координаты курсора мыши (X, Y) легко преобразуются в номера столбца
и строки ячейки (aCol, aRow) при помощи метода:
procedure MouseToCell(X, Y : Integer; var ACol, ARow : Longint);

Существует и обратный метод, возвращающий координаты прямоугольной
области, соответствующей ячейке:
function CellRect(ACol, ARow : Longint): TRect;

Ключевые события сетки
Наиболее популярным событием сетки безусловно является событие, вызываемое при смене выделенной ячейки:
property OnSelectCell: TSelectCellEvent;
type TSelectCellEvent = procedure (Sender: TObject; ACol,
ARow: Longint; var CanSelect: Boolean) of object;

Оно возникает в момент выбора пользователем ячейки сетки с помощью мыши или клавиш управления курсором. Параметры ACol и ARow укажут координаты выбираемой ячейки. Благодаря переменной CanSelect программист в состоянии запретить (CanSelect:=false) или разрешить (true) выбор этой ячейки.
procedure TForm1.DrawGrid1SelectCell(Sender: TObject; ACol, ARow: Integer;
var CanSelect: Boolean);
begin
CanSelect:= NOT ((ACol>3) and (aCol<10) and (aRow=2));
end;

Два события описывают реакцию сетки на перетаскивание колонок и строк:
property OnColumnMoved: TMovedEvent;
property OnRowMoved: TMovedEvent;
type TMovedEvent = procedure (Sender: TObject; FromIndex, ToIndex: Longint) of object;

Имейте в виду, что эти события генерируются только в случае, если в опциях сетки (см. табл. 8.6) применяются флаги goColMoving и goRowMoving. В обработчике событий параметр FromIndex содержит старый индекс столбца/строки, а ToIndex – новое значение индекса.
Несмотря на то что сетка изображений не в состоянии самостоятельно хранить текстовые данные, в ней объявлены три обработчика событий, непосредственно связанные с вводом и редактированием текста. Эти методы це-

191

Сетки

лесообразнее использовать у ее потомка, компонента TStringGrid, конечно,
при условии включения опции goEditing.
При вводе текста прежде всего проверяется соответствие вводимых символов установленной маске. Маска описывается в параметре Value, а правила
ее описания полностью соответствуют маске класса TMaskEdit.
property OnGetEditMask: TGetEditEvent;
type TGetEditEvent = procedure (Sender: TObject; ACol,
ARow: Longint; var Value: string) of object;

При редактировании текст, ранее находящийся в ячейке, заносится в параметр Value.
property OnGetEditText: TGetEditEvent;

По окончании редактирования содержимого ячейки вызывается событие:
property OnSetEditText: TSetEditEvent;
type TSetEditEvent = procedure (Sender: TObject; ACol,
ARow: Longint; const Value: string) of object;

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

Расширенные возможности по оформлению сетки
Ключевым событием сетки изображений считается событие, возникающее
в момент перерисовки ячейки сетки:
property OnDrawCell: TDrawCellEvent;
TDrawCellEvent = procedure (Sender: TObject; ACol, ARow: Longint;
Rect: TRect; State: TGridDrawState) of object;

Обработчик этого события будет вызываться только в случае, если отключена (установлена в false) прорисовка по умолчанию:
property DefaultDrawing: Boolean;

Рисование осуществляется на холсте сетки изображений. Свойство Canvas
унаследовано от класса TCustomControl.
property Canvas: TCanvas;

Параметры события OnDrawCell() описаны в табл. 8.7.
Таблица 8.7. Параметры метода OnDrawCell()
Параметр

Назначение параметра

Sender

Элемент управления.

ACol, ARow

Координаты ячейки.

Rect

Прямоугольная область соответствующей ячейки.

State

gdSelected – ячейка выбрана.
gdFocused – ячейка сетки в фокусе.
gdFixed – ячейка в фиксированной области.

192

Глава 8. Стандартные компоненты

Воплотим приобретенные знания в исходный код. Разместим на форме компонент TDrawGrid, зададим размерность сетки: 1000 колонок на 1000 строк.
Размер ячеек (DefaultColWidth и DefaultRowHeight): 5×5 пикселов. Переведем
свойство DefaultDrawing в false и опишем обработчик события OnDrawCell()
так, как предложено в следующем примере:
procedure TForm1.DrawGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var CellColor : TColor;
begin
with DrawGrid1.canvas do
begin
CellColor:=COLOR_ENDCOLORS + 1 +(ACol*DrawGrid1.RowCount)+(ARow);
Brush.color:=CellColor;
FillRect(Rect);
end;
end;

В результате ячейки сетки заливаются цветами из переменной CellColor.

Сетка строк – компонент TStringGrid
Как уже упоминалось, основное отличие сетки строк от сетки изображений
заключается в способности первой не только отображать, но и хранить текстовые данные. Такая возможность приобретена благодаря появлению нового
свойства:
property Cells[ACol, ARow: Integer]: string;

Это не что иное, как инкапсулированный в сетку набор TStrings, представленный в виде двумерного массива строк размерностью, определенной количеством столбцов и строк [0..ColCount-1, 0..RowCount-1].
StringGrid1.Cells[5,5]:=’Привет’;

Еще один способ доступа к столбцу/строке реализован через свойства:
property Cols[Index: Integer]: TStrings;
property Rows[Index: Integer]: TStrings;

//элементы колонки Index
//элементы ряда Index

Например, таким образом можно заполнить шапку таблицы.
procedure TForm1.FormCreate(Sender: TObject);
var SL:TStringList;
begin
SL:=TStringList.Create;
SL.Add(''); SL.Add('Кол. 1'); SL.Add('Кол. 2'); SL.Add('Кол. 3');
StringGrid1.Rows[0]:=SL;
SL.Clear;
SL.Add(''); SL.Add('Ряд 1'); SL.Add('Ряд 2'); SL.Add('Ряд 3');
StringGrid1.Cols[0]:=SL;
SL.free;
end;

Сетки

193

Помимо строк сетка TStringGrid согласна хранить ссылки на объекты. Ссылки также размещаются в массиве и доступны через свойство:
property Objects [ACol, ARow: Integer]: TObject;

Пожалуй, наступило время серьезного эксперимента. Попробуем собрать
в сетку картинки из набора Delphi (по умолчанию они расположены в каталоге C:\Program Files\Common Files\Borland Shared\Images\Buttons). Для
этого откройте новый проект и разместите в нем сетку строк TStringGrid.
Проведите с ее свойствами следующие манипуляции:
ColCount:=10; RowCount:=20; Align:=alClient.

Теперь вернитесь к форме, найдите в Инспекторе объектов событие OnCreate() и опишите его, как предложено в листинге:
procedure TForm1.FormCreate(Sender: TObject);
var BmpPath: string;
SR: TSearchRec;
I,aCol,aRow: integer;
BMP: TBitmap;
begin
BmpPath:='С:\Program Files\Common Files\Borland Shared\Images\Buttons\';
aCol:=0; aRow:=0;
I:=FindFirst(BmpPath+'*.bmp',faAnyFile,SR);
while I=0 do
begin
StringGrid1.Cells[aCol,aRow]:=SR.Name;
BMP:= TBitmap.Create;
//создаем объект
StringGrid1.Objects[aCol,aRow]:=BMP;
//связываем объект с ячейкой
with StringGrid1.Objects[aCol,aRow] as TBitmap do
LoadFromFile(BmpPath+SR.Name);
aCol:=aCol+1;
if aCol>StringGrid1.ColCount-1 then
begin
aRow:=aRow+1;
aCol:=0;
end;
I:=FindNext(SR);
end;
FindClose(SR);
end;

Как видно из кода, в момент создания формы решаются следующие задачи:
• поиск всех файлов картинок (*.bmp) в каталоге, определенном переменной
BmpPath;
• запись имени файла в ячейку сетки;
• создание объекта класса TBitmap с одновременной установкой ссылки
на этот объект в ячейке сетки строк;
• загрузка изображения в объект;
• переход к новой ячейке.

194

Глава 8. Стандартные компоненты

В исходном коде есть функции FindFirst(), FindNext() и FindClose(), применяемые для поиска файлов и каталогов. Подробнее об этих функциях см.
в главе 4 «Основы работы с файлами».
Обратимся к событию, отвечающему за прорисовку ячеек сетки:
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
begin
if StringGrid1.Objects[ACol,ARow] is TBitmap then
with StringGrid1.Canvas do
Draw(Rect.Left+15,Rect.Top+20, TBitmap(StringGrid1.Objects[ACol,ARow]));
end;

Этих двух процедур было бы вполне достаточно для сбора и отображения
картинок, но следуя правилу, принятому в Delphi, необходимо обеспечить
уничтожение созданных нами объектов.
Еще раз акцентирую внимание на том, что сетка хранит не сами объекты, а только ссылки на них, и с уничтожением таблицы созданные вами объекты не разрушаются. Ответственность за их уничтожение лежит на программисте.

В обработчике события OnCreate() формы проекта мы создаем несколько десятков объектов TBitmap. Предлагаю освободить занимаемые ресурсы в момент закрытия формы.
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var aCol,aRow:integer;
begin
for aCol:=0 to StringGrid1.ColCount-1 do
begin
for aRow:=0 to StringGrid1.RowCount-1 do
begin
if StringGrid1.Objects[aCol,aRow] is TBitmap then
with StringGrid1.Objects[aCol,aRow] as TBitmap do free;
end;
end;
end;

Славно потрудились! А теперь
нажмите клавишу F9. Если
пример был повторен правильно, ячейки сетки заполнятся рисунками из коллекции Delphi (рис. 8.9).

Рис. 8.9. Сетка, заполненная рисунками

195

Меню

Меню
На мой взгляд, присутствие программного меню в приложении является абсолютно необходимым условием для того, чтобы это творение смело могло
называться программным продуктом для Microsoft Windows. Вообще, по
сравнению с другими элементами управления, меню – это одно из самых
удачных дизайнерских решений программистов. Самое главное его достоинство заключается в том, что, практически не занимая места на форме, меню
способно предоставить в распоряжение пользователя всю функциональность
программы.
Различают два типа меню: главное и всплывающее, или контекстное. Классический пример главного меню – меню программ пакета Microsoft Office
или меню среды программирования Delphi. Главное меню размещается сразу под заголовком формы и, как правило, на самом верхнем уровне содержит пункты Файл, Правка, Окно и Справка. При выборе пункта верхнего уровня
из него «выпадают» связанные с ним элементы нижнего уровня.
В процессе проектирования вы можете размещать на форме сколько угодно
компонентов главного меню (TMainMenu), однако после запуска приложение
сможет отобразить только одно главное меню. Ограничений на численность
всплывающих меню у приложения нет. В отличие от главного меню, всплывающее меню возникает на экране только после щелчка правой кнопки мыши по форме или элементу управления.
Программисты стараются не перегружать этот тип меню избыточными
пунктами и по негласной договоренности заполняют его операциями, относящимися именно к тому элементу управления, которому принадлежит это
меню. Всплывающее меню называют контекстным, поскольку оно тесно
связано с его элементом управления.
С точки зрения иерархии наследования как главное, так всплывающее меню берут начало от уже
изученного нами класса TComponent (см. главу 6
«Невидимые классы»). В Delphi реализовано два
компонента, предоставляющих программисту
все возможности главного (TMainMenu) и всплывающего (TPopupMenu) меню (рис. 8.10). Оба компонента вы обнаружите на первой странице стандартных (Standard) элементов управления палитры компонентов Delphi. По сути, оба элемента
управления представляют собой контейнеры,
обеспечивающие хранение и доступ к отдельным
пунктам меню – элементам, построенным на основе класса TmenuItem.

TControl
TMenu
TMainMenu
TPopupMenu

Рис. 8.10. Иерархия меню

Элементы управления TMainMenu и TPopupMenu снабжены специализированным
редактором, значительно упрощающим процесс дизайна меню. Для вызова
редактора меню (рис. 8.11) достаточно дважды щелкнуть левой кнопкой мыши по компоненту меню или выбрать свойство Items в Инспекторе объектов.

196

Глава 8. Стандартные компоненты

Для создания пункта меню достаточно
выделить курсором мыши пустую область и в поле Caption Инспектора объектов присвоить пункту название.
Пункты меню второго уровня способны
обладать подменю, для создания которого достаточно щелкнуть правой кнопкой мыши и во всплывающем меню выбрать пункт Create Submenu. При необхоРис. 8.11. Редактор меню
димости можно пересортировать пункты внутри меню, перетаскивая их мышью. И наконец, для удаления
лишнего пункта просто нажмите клавишу Del.

Элемент меню TMenuItem
Вне зависимости от того, с каким типом меню (главным или всплывающим)
вы собираетесь работать, основным вашим помощником будет элемент меню –
объект типа TMenuItem. Объект TMenuItem не может существовать в отрыве от
главного или всплывающего меню. По сути, TMainMenu и TPopupMenu являются
хранилищами элементов TMenuItem.
Как и большинство уже изученных элементов управления, пункт меню обладает заголовком, определяемым свойством:
property Caption: string;

Однако свойство Caption элемента меню обладает особенностями, отличающими его от других компонентов Delphi. Во-первых, если в Caption ввести
единственный символ «–» (тире), то пункт меню превратится в разделитель.
Так, на рис. 8.11 между элементами Сохранить как и Закрыть проведена сплошная горизонтальная линия. Это и есть пункт меню разделитель. Он не способен нести функциональную нагрузку и вызывать связанную с ним процедуру; его задача – улучшение наглядности приложения.
Во-вторых, свойство Caption позволяет определить клавиши-акселераторы,
ускоряющие доступ пользователя к пункту меню при одновременном нажатии клавиши Alt и клавиши-акселератора. На экране символ, соответствующий клавише-акселератору, выводится с подчеркиванием. Для того чтобы
определить акселератор во время набора заголовка меню, необходимо ввести
символ & перед символом, который предполагается сделать акселератором,
например &Файл или О&кно.
Для того чтобы выяснить, какому компоненту принадлежит элемент меню,
вызывают метод:
function GetParentMenu: TMenu;

Чтобы исключить ошибки назначения одинаковых акселераторов для различных пунктов меню, следует установить в автоматический режим (maAutomatic) свойство:
property AutoHotkeys: TMenuItemAutoFlag;

Меню

197

type TMenuItemAutoFlag = (maAutomatic, maManual, maParent); //по умолчанию maAutomatic

В этом случае, перед тем как меню будет отображено на экране, Delphi выявит и отключит дубликаты акселераторов. То же самое можно сделать, вызвав метод:
function RethinkHotkeys: Boolean;

Если функция вернула true, это признак того, что были найдены и исправлены ошибки.
Помимо акселераторов с каждым пунктом меню можно связать так называемые «быстрые клавиши». Отличие быстрых клавиш от клавиш-акселераторов заключается в том, что выбор акселератора позволит лишь добраться до
необходимого пункта меню, а нажатие комбинации быстрых клавиш заставит приложение выполнить сопоставленный с ними пункт меню. Итак, быстрые клавиши определяются свойством:
property ShortCut : TShortCut;

При желании сменить быстрые клавиши во время выполнения программы
следует воспользоваться специальной функцией, преобразующей комбинацию клавиш к виду TShortCut:
function ShortCut(Key: Word; Shift: TShiftState): TShortCut;

Здесь первым параметром передается код символа, а вторым – код нажатой
служебной клавиши, например
MenuItem1.ShortCut := ShortCut(Word('V'), [ssCtrl]);

Стоит подчеркнуть, что функция не является внутренним методом класса
TMenuItem. Это самостоятельная функция, которая наряду с десятком других
методов входит в обойму так называемых «методов поддержки меню» и объявлена в модуле Menus.
Основное назначение пункта меню – среагировать на щелчок пользователя.
В этом он практически ничем не отличается от уже знакомых нам элементов
управления. Соответственно ничем не отличается и ключевой для элемента
TMenuItem обработчик события:
property OnClick: TNotifyEvent;

Хотя у пункта меню не предусмотрена обработка двойного щелчка, но тем не
менее такое действие пользователя совсем не исключается. Если произведен
двойной щелчок по элементу меню, обладающему подменю, то в списке дочерних пунктов меню Delphi постарается найти элемент, чье свойство Default установлено в true:
property Default: Boolean;

Если такой элемент существует, будет вызван его обработчик OnClick(). Другими словами, это элемент, вызываемый по умолчанию.
Вместе с тем пункт меню нельзя считать тривиальным элементом управления. В силах программиста превратить отдельный пункт меню в элемент

198

Глава 8. Стандартные компоненты

управления, напоминающий флажок (компонент TCheckBox) или переключатель (компонент TRadioButton). Таким чудесным возможностям TMenuItem в первую очередь обязан свойству:
property Checked: Boolean;

Установив это поле в true, мы увидим «галочку» слева от заголовка. Этим самым пункт меню сигнализирует нам, что он помечен. Вот вам и аналог элемента управления TCheckBox. Возможность контроля этого свойства позволяет использовать конструкцию IF…THEN…ELSE в обработчике события OnClick():
procedure TForm1.MenuItem1Click(Sender: TObject);
begin
if MenuItem1.Checked=true then …
//операция 1
else …; //операция 2
end;

Для того чтобы пункт меню при щелчке по нему автоматически помечался
галочкой, следует перевести в true свойство:
property AutoCheck: Boolean;

иначе это придется делать вручную внутри события OnClick(). В простейшем
случае это будет всего одна строка кода, инвертирующая предыдущее состояние пункта меню:
MenuItem1.Checked:= NOT MenuItem1.Checked;

Для того чтобы научиться превращать пункт меню в переключатель, рассмотрим небольшой пример. Создайте новый проект, разместите на нем компонент TMainMenu. Создайте пункт меню верхнего уровня с заголовком «Цвет
формы» и три подчиненных ему пункта: «Красный» с именем Name:= miRed,
«Синий» с именем miBlue и «Зеленый» с именем miGreen. Наша задача –
щелчком по меню перекрашивать форму в соответствующий цвет. Теперь
одновременно выделите все три только что созданные пункта меню и найдите в Инспекторе объектов свойство GroupIndex, предназначенное для создания логических групп пунктов меню:
property GroupIndex: Byte;

По умолчанию каждый вновь создаваемый элемент TMenuItem не входит ни
в одну группу (GroupIndex = 0), но если полю GroupIndex двух или более пунктов меню присвоить значение, отличное от нуля, то мы получим возможность объединить элементы в группу. Установим это свойство, скажем в 10.
Следующим шагом по превращению трех наших пунктов меню в группу переключателей будет установка в true свойства:
property RadioItem: Boolean;

Теперь пункт меню будет вести себя аналогично переключателю (TRadioButton). Для того чтобы выяснить, не отмечен ли элемент меню флажком, надо
обратиться к уже знакомому нам свойству Checked, а Delphi позаботится о том,
чтобы в одной группе переключателей не могло быть помечено более одного
элемента TMenuItem одновременно. И в заключение проверьте, чтобы свойство

199

Меню

AutoCheck всех трех элементов было переведено в true. После этого опишите обработку OnClick() для каждого из пунктов меню, например для красного:
Form1.Color:=clRed;

Наша программка готова… Доступ к уже существующим пунктам меню мы
получим, обратившись к свойству Items родительского элемента:
property Items[Index: Integer]: TMenuItem;

Здесь хранится список всех дочерних элементов меню; нам надо только выбрать его порядковый индекс. Информацию о количестве пунктов меню предоставит свойство:
property Count: Integer;

//только для чтения

Каждый пункт меню знает, кому он принадлежит и свой индекс в списке
этого родительского элемента:
property Parent: TMenuItem;
property MenuIndex: Integer;

//родитель
//индекс

Единственное исключение при определении родительского пункта меню
возникает, если элемент меню является пунктом самого верхнего уровня.
Свойство MenuIndex сослужит отличную службу, если необходимо пересортировать пункты меню. Например, для перемещения элемента меню в начало
списка присвойте этому свойству нулевое значение.
Если родительский пункт меню захочет выяснить индекс пункта меню в своем списке Items, то для этой цели стоит обратиться к методу:
function IndexOf(Item: TMenuItem): Integer;

Если меню не входит в подменю родительского элемента, функция вернет –1.
Для поиска пункта меню по его заголовку применяется функция Find().
В случае успеха метод вернет ссылку на найденный пункт меню, иначе получаем пустышку – неопределенный указатель nil.
function Find(ACaption: string): TMenuItem;

Присвоение пиктограмм элементам меню
Для придания пункту меню более изысканного вида рядом с заголовком разрешается расположить небольшую пиктограмму. Вариантов решения этой
задачи два. Простейший из них сводится к элементарной загрузке изображения в свойство:
property Bitmap: TBitmap;

Второй вариант более рационален и заключается в подключении к компоненту TMainMenu (TPopupMenu) коллекции изображений – компонента TImageList. При помощи свойства ImageIndex можно выбрать любое изображение
из коллекции по его индексу и отобразить слева от заголовка пункта меню:
property ImageIndex: TImageIndex;

200

Глава 8. Стандартные компоненты

При желании можно назначить отдельную коллекцию картинок всем пунктам подменю. Для этого у владельца подменю надо найти свойство:
property SubMenuImages: TCustomImageList;

Создание элементов меню во время выполнения программы
Как и все компоненты коллекции VCL, пункт меню вооружен своим конструктором и деструктором. Эти методы выполняют задачи по созданию и уничтожению элемента меню:
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;

При создании нового экземпляра класса TMenuItem конструктор делает пункт
меню видимым (Visible=true) и включенным (Enabled=True). В качестве владельца (AOwner) нового элемента TMenuItem следует назначать пункт меню,
к которому мы рассчитываем подключить новый элемент. Операция присоединения нового пункта меню осуществляется одной из следующих функций:
procedure Add(Item: TMenuItem); overload;
procedure Add(const AItems: array of TMenuItem); overload;
procedure Insert(Index: Integer; Item: TMenuItem);

Первые два метода перегружаемые и поэтому называются одинаково – Add().
Разница между ними в том, что первый добавляет только один элемент Item,
а второй способен подключить целый массив пунктов меню AItems. Если методы Add() присоединяют новые пункты меню к концу списка, то метод Insert()
вставит новый пункт меню Item в позицию, определяемую параметром Index.
В качестве индексов группировки старайтесь не использовать значения 1, 3 и 5.
Эти значения применяются при слиянии меню приложения и серверов OLE-объектов.
Впрочем, если в приложении не планируется работа с OLE, то можно не обращать
внимания на это ограничение. Подробнее о технологии OLE 2.0 мы поговорим
в главе 26 «Связывание и внедрение объектов – технология OLE».

Допустим, что в нашем проекте существует некий пункт меню с именем miMenuItem. Тогда простейший пример создания нового пункта меню и присоединения его к пункту miMenuItem программным способом будет выглядеть
примерно так:
procedure TForm1.miFileClick(Sender: TObject);
var mi:TMenuItem;
begin
mi:=TMenuItem.Create(miMenuItem); //создание нового пункта меню mi
mi.Caption:='Новый пункт меню';
//назначение заголовка этому пункту
miMenuItem.Add(mi);
//присоединение нового пункта меню к miMenuItem
end;

А теперь реализуем пример посложнее. Наше приложение будет создавать
три новых пункта меню и назначать им обработчики события OnClick(). Начните новый пустой проект и разместите на нем всего один компонент – глав-

201

Меню

ное меню. В свою очередь в главном меню определите один-единственный
пункт меню с именем miMenuItem. В секции частных объявлений проекта (private) опишите процедуру NewMenuClick(). Она будет выполнять роль обработчика события OnClick() для новых пунктов меню. Сами пункты меню создаются при вызове процедуры OnCreate() главной формы проекта. Все остальное вы найдете в следующем листинге:
unit Unit1;
interface
uses Windows, Graphics, Controls, Forms, Menus, Classes;
type
TForm1 = class(TForm)
MainMenu1: TMainMenu;
miMenuItem: TMenuItem;
procedure FormCreate(Sender: TObject);
private
procedure NewMenuClick(Sender: TObject);
end;
var Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var AItems: array of TMenuItem;
i:integer;
begin
SetLength(AItems,3); //распределяем память динамического массива для 3-х элементов
AItems[0]:=TMenuItem.Create(miMenuItem); //создаем первый элемент
AItems[0].Caption:='Красный';
//назначаем ему заголовок
AItems[1]:=TMenuItem.Create(miMenuItem);
AItems[1].Caption:='Синий';
AItems[2]:=TMenuItem.Create(miMenuItem);
AItems[2].Caption:='Зеленый';
for I:=Low(AItems) to High(AItems) do {инициализируем созданные пункты меню}
begin
AItems[I].Tag:=I;
//пункты меню различаются значением в свойстве Tag
AItems[I].AutoCheck:=True;
AItems[I].RadioItem:=True;
AItems[I].OnClick:=NewMenuClick; //назначаем реакцию на щелчок по пункту
end;
miMenuItem.Add(AItems); //подключаем все три новых пункта меню к элементу miMenuItem
AItems[0].Click;
end;

//программным образом щелкаем по пункту меню 'Красный'

procedure TForm1.NewMenuClick(Sender: TObject);
begin
{Процедура проверяет свойство Tag источника Sender и в зависимости от значения
перекрашивает форму в один из трех цветов}

202

Глава 8. Стандартные компоненты

case (Sender as TComponent).Tag of
0: Form1.Color:=clRed;
1: Form1.Color:=clGreen;
2: Form1.Color:=clBlue;
end;
end;
end.

Более подготовленным программистам полезно знать, что с пунктом меню связан
ряд сообщений Windows, которые отправляются операционной системой в оконную
процедуру (см. главу 27 «Программирование на Win32 API») окна, владеющего этим
меню. Например, в момент активизации меню будет послано сообщение WM_SYSCOMMAND, при получении фокуса элементом меню – сообщение WM_MENUSELECT. Однако
из всего списка сообщений меню самым важным является WM_COMMAND. Именно оно
будет послано окну в момент щелчка пользователем по элементу меню (при условии, что с этим элементом связана какая-либо команда).
Для того чтобы оконная процедура была в состоянии выяснить, по какому именно
пункту меню был произведен щелчок, в качестве старшего параметра (wParam) сообщения передается уникальный числовой идентификатор элемента TMenuItem.
Если по какой-либо причине вам понадобится этот идентификатор, то прочитать
его можно из свойства:
property Command: Word;

//только для чтения

Удаление элементов меню
Для удаления дочернего меню из списка необходим его индекс и процедура:
procedure Delete(Index: Integer);

Еще один вариант удаления пункта меню:
procedure Remove(Item: TMenuItem);

Здесь вместо индекса удаляемого элемента требуется указать непосредственно удаляемый пункт. Однако эти методы не уничтожают элемент меню,
а лишь отнимают его у родительского компонента. Для физического удаления пункта используйте метод Free. Самый кардинальный метод удаления:
procedure Clear;

Он расправится сразу со всеми дочерними пунктами меню, причем в этом
случае явный вызов метода Free() не требуется – все пункты меню автоматически освободят занимаемую память.

Элементы-разделители
В меню, перенасыщенном командами, пункты меню формируют столбцы неоправданно больших размеров. В таких случаях весьма полезным окажется
свойство:
type TMenuBreak = (mbNone, mbBreak, mbBarBreak);
property Break: TMenuBreak;

Меню

203

Если одному из пунктов меню (находящемуся примерно в середине столбца)
установить это свойство в mbBreak, то следующие за ним элементы меню
на экране монитора создадут еще один столбец. Если свойство принимает
значение mbBarBreak, то столбцы будут разделены вертикальной чертой. Появление нового столбца – только визуальный эффект; принадлежность элементов меню в этом случае не меняется.
Для визуального объединения нескольких пунктов меню в группу или для
отделения одного элемента меню от другого программисты часто применяют
пункты-разделители. Для этого во время разработки программы достаточно
в свойство Caption поместить один-единственный символ «–» (тире). Во время выполнения программы задачу вставки линий-разделителей решают две
пары функций:
function InsertNewLineBefore(AItem: TMenuItem): Integer;
function InsertNewLineAfter(AItem: TMenuItem): Integer;

и
function NewTopLine: Integer;
function NewBottomLine: Integer;

Метод InsertNewLineBefore() вставит разделитель перед пунктом меню AItem,
а метод InsertNewLineAfter() – после этого пункта. Пара функций NewTopLine
и NewBottomLine обычно используется при создании подменю во время выполнения программы. Все методы возвращают индекс элемента-разделителя.
При вставке разделителей программным способом разработчики Delphi рекомендуют устанавливать в автоматический режим (maAutomatic) свойство:
property AutoLineReduction: TMenuItemAutoFlag;
TMenuItemAutoFlag = (maAutomatic, maManual, maParent);

В этом случае ответственность за удаление лишних разделителей возьмет
на себя Delphi. Под лишними разделителями Delphi понимает следующие:
два разделителя подряд, появление разделителя на самой первой или самой
последней позиции подменю и т. п. Однако если вы отказались от автоматической чистки разделителей, то ненужные пункты меню превосходно удалит метод:
function RethinkLines: Boolean;

В случае успеха функция возвратит значение true. Если пункт меню является обычной разделительной линией, то при вызове метода IsLine последний
вернет значение true:
function IsLine: Boolean;

Особенности перерисовки пункта меню
Элемент меню способен реагировать на четыре события, основным из которых считается щелчок OnClick() – свидетельство выбора этого пункта меню
пользователем. Все остальные обработчики предоставляют дополнительные
возможности по прорисовке этого элемента меню. Простейший из них:

204

Глава 8. Стандартные компоненты

property OnDrawItem: TMenuDrawItemEvent;
type TMenuDrawItemEvent = procedure (Sender: TObject; ACanvas: TCanvas;
ARect: TRect; Selected: Boolean) of object;

где ACanvas – холст пункта меню, ARect – координаты границ холста, доступные для перерисовки, а параметр Selected сигнализирует, выбран данный
пункт меню или нет.
Рассмотрим пример использования OnDrawItem():
procedure TForm1.MenuItem1DrawItem(Sender: TObject; ACanvas: TCanvas;
ARect: TRect; Selected: Boolean);
begin
with ACanvas do
begin
FillRect(aRect);
if Selected=True then Font.Style:=Font.Style+[fsUnderline]
else Font.Style:=Font.Style-[fsUnderline];
TextOut(aRect.Left, aRect.Top, (Sender as TMenuItem).Caption);
end;
end;

В событии OnDrawItem() пункта меню проверяется, выделен он пользователем
или нет. Если пункт меню выделен, его заголовок подчеркивается, иначе
выводится обычным шрифтом. Второй способ прорисовки пункта меню обладает более богатыми возможностями:
property OnAdvancedDrawItem: TAdvancedMenuDrawItemEvent;
type TAdvancedMenuDrawItemEvent = procedure (Sender: TObject; ACanvas: TCanvas;
ARect: TRect; State: TOwnerDrawState) of object;

Расширенные возможности обеспечиваются наличием параметра State, сообщающего текущее состояние данного пункта меню. Теперь мы можем узнать не только о том, выделен этот пункт меню или нет, но и проконтролировать, отмечен ли он галочкой, активен или пассивен и т. д.
type TOwnerDrawState = set of (odSelected, odGrayed, odDisabled, odChecked,
odFocused, odDefault, odHotLight, odInactive, odNoAccel, odNoFocusRect,
odReserved1, odReserved2, odComboBoxEdit);

Вызов обработчиков событий OnDrawItem() и OnAdvancedDrawItem() произойдет
только при условии, что свойство OwnerDraw владельца пункта меню (TMainMenu
или TPopupMenu) установлено в true.

И наконец, третий обработчик события, связанный с прорисовкой пункта
меню, решает задачу по динамическому изменению размеров пункта меню.
property OnMeasureItem: TMenuMeasureItemEvent;
type TMenuMeasureItemEvent = procedure (Sender: TObject; ACanvas: TCanvas;
var Width, Height: Integer) of object;

В этом случае ключевыми параметрами будут ширина (Width) и высота
(Height) пункта меню. Этот обработчик может пригодиться, если, например,
требуется ограничить максимальный размер элемента меню.

Меню

205

Класс TMenu
Класс TMenu является каркасом для компонентов TMainMenu и TPopupMenu. Основное назначение класса – служить хранилищем для элементов меню –
экземпляров класса TMenuItem. Для этой цели в нем реализовано уже знакомое нам свойство:
property Items: TMenuItem; default;

благодаря которому мы получаем доступ к тому или иному элементу меню,
указав соответствующий ему индекс. Например:
MyItem:=MainMenu1.Items.Item[0];

При изменении состава заключенных в меню элементов, при загрузке меню
в память и при изменении свойств, влияющих на структуру меню, возникает событие:
property OnChange: TMenuChangeEvent;
type TMenuChangeEvent = procedure (Sender: TObject; Source: TMenuItem;
Rebuild: Boolean) of object;

где Source указывает на элемент меню, чьи свойства изменяются. Если при
этом параметр Rebuild возвращает значение true, то это свидетельствует
о том, что осуществляются кардинальные изменения, связанные с удалением или созданием элементов меню.

Главное меню – компонент TMainMenu
Про возможности главного меню мы уже практически все знаем. Класс TMainMenu построен на основе TMenu и состоит из элементарных пунктов меню TMenuItem. Чтобы завершить картину, познакомимся с полезной особенностью
главного меню – механизмом объединения. Объединение меню применяется:
1. При работе с приложениями с интерфейсом MDI (см. главу 9 «Форма, интерфейсы SDI и MDI»).
2. При разработке приложения, обладающего несколькими окнами со своими меню.
3. При внедрении OLE-объектов (например, с применением компонента
TOleConteiner). В этом случае при старте сервера автоматизации последний вставляет свои элементы меню в главное меню приложения.
Последовательно рассмотрим все три направления использования механизма слияния меню. Первый случай – отображение в главном меню главной
формы приложения MDI пунктов меню с названиями открытых дочерних
окон. По большому счету говорить об этой операции как о механизме объединения меню является некоторой натяжкой, но, с другой стороны, благодаря этой операции пользователь получает удобную возможность обращения
к открытым дочерним окнам.
Как ни странно это покажется на первый взгляд, но для решения этой задачи
надо воспользоваться не свойствами или методами класса TMainMenu, а свойством WindowMenu главной формы нашего проекта. Этим свойством определяется

206

Глава 8. Стандартные компоненты

пункт меню TMenuItem (входящий в состав элементов главного меню), к которому будет «пристроен» список имен дочерних форм.
Если разрабатываемое приложение строится на основе интерфейса SDI, то
для автоматического присоединения меню подчиненной формы к меню
главной формы проверьте, чтобы свойство AutoMerge главного меню подчиненной формы было установлено в true:
property AutoMerge: Boolean;

Свойству AutoMerge компонента TMainMenu главной формы проекта всегда должно
быть присвоено значение False.

Если требуется полностью контролировать процесс слияния меню, то вместо
услуг свойства AutoMerge стоит обратиться к методам:
procedure Merge(Menu: TMainMenu);
procedure Unmerge(Menu: TMainMenu);

Эти процедуры предназначены для присоединения и отсоединения пунктов
меню из текста программы. В параметре Menu следует передавать ссылку
на главное меню проекта.
Если в приложении используется контейнер OLE-объектов – компонент
TOLEContainer, то стоит знать о существовании трех методов главного меню,
обеспечивающих слияние меню приложения и сервера OLE:
procedure GetOle2AcceleratorTable(var AccelTable: HAccel; var AccelCount: Integer;
const Groups: array of Integer);
procedure PopulateOle2Menu(SharedMenu: HMenu; const Groups: array of Integer;
var Widths: array of Longint);

Для осуществления временной замены главного меню на меню сервера OLE
вызывают процедуру:
procedure SetOle2MenuHandle(Handle: HMENU);

В качестве параметра Handle выступает указатель на меню сервера. Для восстановления оригинального меню вместо указателя передают нулевое значение.

Всплывающее меню – компонент TPopupMenu
Подавляющая часть визуальных элементов управления, построенных на основе класса TControl, обладает суверенным правом показа всплывающего,
или контекстного, меню. Для этого элементы оснащены свойством, предназначенным для подключения к ним компонента TPopupMenu:
property PopupMenu: TPopupMenu;

В свою очередь компонент TPopupMenu также в состоянии идентифицировать
«хозяина» при помощи своего свойства:
property PopupComponent: TComponent;

Если всплывающее меню разделяется двумя и более компонентами, то трудно предсказать, на кого укажет данное свойство, если меню еще ни разу не

Резюме

207

вызывалось на экран. Если же меню отображалось, то в свойстве окажется
ссылка на тот элемент управления, который воспользовался этим контекстным меню последним.
По умолчанию всплывающее меню появляется рядом со своим владельцем
после щелчка правой кнопкой мыши по его поверхности, а координаты вывода меню определяются текущим местоположением указателя мыши.
Единственное, на что можно повлиять в этом случае, – так это определить,
где мы предпочитаем увидеть меню: левее (paLeft), правее (paRight) или по
центру (paCenter) относительно все того же указателя мыши.
property Alignment: TPopupAlignment;
type TPopupAlignment = (paLeft, paRight, paCenter);

При желании можно отказаться от автоматического вывода всплывающего
меню на экран, для чего требуется установить в False свойство:
property AutoPopup: Boolean;

С этого момента управление показом меню переключается в ручной режим.
Теперь для вызова меню потребуется вспомнить о существовании процедуры:
procedure Popup(X, Y: Integer); virtual;

Управление отображением контекстного меню с помощью этого метода имеет одно существенное преимущество: мы получаем право определить место
вывода меню, указав экранные координаты X и Y явным образом. В момент
вывода меню на экран происходит событие:
property OnPopup: TNotifyEvent;

Это событие зачастую применяется для последней настройки пунктов контекстного меню – управления свойствами Visible, Enabled и Checked.
Процессу отображения пунктов меню на экране можно придать современный вид, включив модные визуальные эффекты «всплывания», но, вспомнив о видеокартах несчастных пользователей, лучше установить это свойство в состояние maNone (отказ от эффектов):
property MenuAnimation: TMenuAnimation;
type TMenuAnimations = (maLeftToRight, maRightToLeft, maTopToBottom,
maBottomToTop, maNone);
TMenuAnimation = set of TMenuAnimations;

Резюме
История практически всех стандартных компонентов начиналась еще во
времена первых версий Microsoft Windows. Их ключевые особенности – простота, наглядность и нетребовательность к системным ресурсам. Опираясь
на компоненты со страницы Standard, вполне реально создавать проекты малой и средней степени сложности. Логическим развитием стандартных элементов управления считаются компоненты со страниц Additional и Win32, которым и будут посвящены следующие главы.

9
Форма, интерфейсы SDI и MDI
Вы можете быть профессионалом-программистом в среде Delphi и создавать
неповторимые по изяществу и совершенству программные продукты, но при
этом не иметь представления о существовании какого-то компонента, скажем TSuperComponent, в стандартной поставке Delphi. Вы обходитесь и без него, причем никто этого не замечает. Но вы вряд ли захотите игнорировать
«центр мироздания» приложений Delphi – класс TForm.
Наиболее продвинутый программист, прочитав эти строки, ухмыльнется и
в два счета напишет консольное приложение, в котором формой и не пахнет,
или создаст окно (синоним формы в терминологии Delphi), обойдя класс TForm
с помощью внутренних функций Windows. Бесспорно, эти направления до
сих пор актуальны, а в исключительных случаях являются единственным
методом решения стоящих перед нами задач. Но еще раз отмечу: именно
в исключительных случаях, а в остальных 99% мы воспользуемся формой.

Форма – TForm
Как можно видеть из иерархии наследования (рис. 9.1),
большую часть свойств и методов форма приобретает от
своих предков. Многие из них были рассмотрены в предыдущих главах и, надеюсь, стали нашими хорошими знакомыми. Вместе с тем класс TForm обладает рядом специфичных возможностей, которые больше не встречаются
ни в одном из компонентов.
При разработке нового программного продукта прежде
всего необходимо определить стиль главной формы приложения:
property FormStyle: TFormStyle;
type TFormStyle = (fsNormal, fsMDIChild, fsMDIForm, fsStayOnTop);

TWinControl
TScrollingWinControl
TCustomForm
TForm

Рис. 9.1. Место
TForm в VCL

209

Форма – TForm

По умолчанию стиль вновь созданной формы инициализируется значением
fsNormal. Этот стиль предназначен для построения приложений с интерфейсом SDI (Single Document Interface, однодокументный интерфейс). Если проектируется программа с интерфейсом MDI (Multi Document Interface, многодокументный интерфейс), то стиль родительской формы определяется как
fsMDIForm. Стиль дочерних окон приложения MDI определяется значением
fsMDIChild. И наконец, стиль fsStayOnTop заставит форму разместиться поверх других окон, причем даже при потере фокуса такая форма будет попрежнему располагаться над формами данного приложения.
Местоположение формы на экране монитора зависит от значения, установленного в свойстве:
property Position: TPosition; //по умолчанию poDesigned
type TPosition = (poDesigned, poDefault, poDefaultPosOnly, poDefaultSizeOnly,
poScreenCenter, poDesktopCenter, poMainFormCenter, poOwnerFormCenter);

При создании новой формы ее свойство Position принимает значение poDesigned. В такой ситуации местоположение и размеры формы во время выполнения приложения будут определяться размерами и местоположением
во время разработки (свойствами Top и Left).
Интересно поведение формы при установке этого свойства в poDefault. При
первом запуске приложения форма отображается в левом верхнем углу экрана, а при каждом последующем выводе на экран форма будет автоматически смещаться вправо и вниз. Высота и ширина формы определяются операционной системой и не зависят от указаний программиста.
Если такое поведение формы устраивает, но при этом требуется сохранить
назначенные в Инспекторе объектов размеры, то выбирают значение poDefaultPosOnly. Обратная ситуация произойдет, если свойство Position установлено в poDefaultSizeOnly. Теперь Windows станет выводить форму в месте, установленном программистом, но ответственность за размеры сторон заберет
в свои руки.
При установке Position в состояние poScreenCenter форма появится точно в центре экрана, а для
центровки формы относительно
рабочего стола следует выбрать
poDesktopCenter. На поведение
вторичных форм проекта влияют poMainFormCenter и poOwnerFormCenter. В первом случае дополнительные формы позиционируются по центру главной
формы, а во втором – по центру
формы-владельца.

Пиктограмма формы

Заголовок формы

Стандартные
кнопки формы
Вертикальная
полоса прокрутки

Клиентская область формы

Горизонтальная полоса прокрутки

Рис. 9.2. Внешний вид стандартной формы

Перечень стандартных кнопок-пиктограмм, располагающихся в правом
верхнем углу формы, задается свойством:

210

Глава 9. Форма, интерфейсы SDI и MDI

type TBorderIcon = (biSystemMenu, biMinimize, biMaximize, biHelp);
TBorderIcons = set of TBorderIcon;
property BorderIcons:TBorderIcons;

По умолчанию отображаются кнопки вызова системного меню, сворачивания и разворачивания окна.
Кнопка закрытия окна выводится практически во всех окнах Windows. Отображение других кнопок зависит от комбинации значений свойств BorderIcons и BorderStyle формы.

Щелчок пользователя по кнопкам сворачивания и разворачивания окна изменяет значение свойства WindowState:
property WindowState: TWindowState;
type TWindowState = (wsNormal, wsMinimized, wsMaximized);

где wsNormal – форма в нормальном состоянии, wsMinimized – форма свернута,
wsMaximized – форма развернута до максимального размера. Для принудительного сворачивания или разворачивания формы этим свойством допускается пользоваться и во время выполнения приложения.
Существенное влияние на внешний вид и поведение формы оказывает стиль
обрамления окна:
property BorderStyle: TFormBorderStyle

Шесть возможных типов бордюра формы описаны в табл. 9.1.
Таблица 9.1. Стили бордюра TFormBorderStyle
Значение

Описание

bsDialog

Размеры формы неизменяемые, бордюр как у стандартного окна диалога.

bsSingle

Размеры формы неизменяемые, простой бордюр.

bsNone

Размеры формы неизменяемые, границы бордюра невидимы.

bsSizeable

Обычная форма.

bsToolWindow Форма с простым бордюром и малой областью под заголовок. Из стандартных кнопок формы выводится только кнопка закрытия окна.
bsSizeToolWin Аналогично bsToolWindow, но с возможностью изменять размеры формы.

По умолчанию толщина бордюра (отступ от границы окна до его клиентской
части) минимальна – 0 пикселов. Для увеличения отступа измените значение в свойстве:
property BorderWidth: TBorderWidth;
type TBorderWidth = 0..MaxInt;

Форма способна обладать одним главным (TMainMenu) и неограниченным количеством всплывающих меню (TPopupMenu). Для взаимодействия с компонентами меню предусмотрены свойства Menu и PopupMenu. При размещении

Форма – TForm

211

на рабочей форме главного меню (класс TMainMenu) в свойстве Menu автоматически создается ссылка на этот компонент:
property Menu: TMainMenu;

Возможность подключения всплывающего меню унаследована от дальнего
родственника – TControl. Для этого у формы объявлено свойство:
property PopupMenu: TPopupMenu;

Родительская форма MDI-приложения владеет секретом, позволяющим автоматически добавлять в основное меню (TMainMenu) главной формы приложения список открытых документов – дочерних форм MDI. Для этого
в свойстве главной формы WindowMenu укажите пункт меню, к которому будет
присоединен список дочерних форм:
property WindowMenu: TMenuItem;

Как правило, список открытых документов добавляется к пункту Окно (Window). И еще одно примечание: в качестве WindowMenu стоит назначать только
пункт меню самого верхнего уровня.
Практически все окна Windows в левом углу заголовка отображают пиктограмму приложения. По умолчанию пиктограмму вновь создаваемого приложения определяет Delphi, назначая стандартную картинку. Если она вас
не устраивает, то с помощью свойства Icon можно связать с формой другое
изображение:
property Icon: TIcon;

Для подключения файла помощи к форме укажите путь к файлу в свойстве
HelpFile. Тогда при обращении к справке из данной формы поиск темы
справки будет осуществляться в указанном файле.
property HelpFile: string;

Если форма предназначена стать контейнером для объектов OLE, то стоит
обратить внимание на свойство:
property OleFormObject: IOleForm;

Текущее состояние формы определяется с помощью свойства:
property FormState: TFormState;
type TFormState = set of (fsCreating, fsVisible, fsShowing, fsModal,
fsCreatedMDIChild, fsActivated);

Значение свойства FormState автоматически устанавливается системой и доступно программисту только для чтения. В момент выполнения конструктора формы (создания формы) свойству FormState соответствует значение fsCreating. Если форма видима – значение fsVisible. Если форма создана как модальное окно – fsModal. Во время разворачивания окна – fsShowing. Если форма является родительским окном в MDI-приложении и создана дочерняя
форма – fsCreatedMDIChild.

212

Глава 9. Форма, интерфейсы SDI и MDI

После поступления в адрес окна сообщения CM_ACTIVATE свойству FormState соответствует значение fsActivated. Это сообщение отправляется операционной системой при получении формой фокуса ввода (окно стало активным).
Состояние fsActivated окна продублировано еще в одном свойстве:
property Active: Boolean;

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

Также продублировано значение fsVisible. Видимость формы определяется
свойством Visible. Если форма видима (но необязательно активна), свойство
устанавливается в true.
property Visible: Boolean;

Перевод окна в видимое состояние можно производить с помощью методов Show()
или ShowModal().

Родительская форма, содержащая оконные элементы управления, всегда обладает информацией об активном (владеющем фокусом ввода) компоненте:
property ActiveControl: TWinControl;

Это свойство также позволяет установить фокус ввода на один из оконных
элементов управления. Кроме того, свойство доступно и во время разработки, и если программист укажет в этом свойстве на какой-нибудь компонент,
то выбранный элемент управления получит фокус ввода при активизации
формы. Напоминаю, что в фокусе ввода может находиться только один
оконный (потомок класса TWinControl) элемент управления.
Для того чтобы во время работы приложения передать фокус другому, размещенному на форме компоненту, кроме выше предложенного свойства
можно применить метод:
procedure FocusControl(Control: TWinControl);

Для решения обратной задачи – снятия фокуса с элемента управления –
предназначен метод:
procedure DefocusControl(Control: TWinControl; Removing: Boolean);

Если форма проекта содержит элементы управления OLE, то по аналогии
с обычными компонентами элемент OLE может быть активизирован путем
установки ссылки на объект OLE в свойстве:
property ActiveOLEControl: TWinControl;

Размеры клиентской части формы (см. рис. 9.2) можно узнать из свойств:

Форма – TForm

213

property ClientHeight: Integer;
property ClientWidth: Integer;

Указанные выше свойства доступны только в режиме для чтения и тесно
взаимосвязаны со свойствами Height и Width. Другими словами, для модификации размеров клиентской части необходимо изменить высоту и ширину
всей формы. Кроме того, информацию о геометрических размерах клиентской части окна можно почерпнуть из свойства ClientRect. Тип TRect хранит
координаты верхнего левого и нижнего правого угла клиентской области.
property ClientRect: TRect;

Программистам, предполагающим использовать в своей работе указатели
(например, при работе с функциями Windows API), полезно помнить, что дескриптор окна содержится в свойстве Handle, а дескриптор клиентской области окна – в свойстве ClientHandle:
property Handle: HWND;
property ClientHandle: HWND;

Оба указателя доступны только для чтения и автоматически устанавливаются в момент создания формы.
Форма вполне способна участвовать в операциях drag-and-drop (подробнее
см. в главе 6). Свойство определяет, является ли форма адресатом операции
перетаскивания:
property DropTarget: Boolean;

Полосы прокрутки
Форма может иметь полосы прокрутки благодаря наличию в своей иерархии
наследования класса TScrollingWinControl. Их появление является признаком того, что геометрические размеры окна не позволяют вместить в себя
все компоненты, размещенные в его клиентской области. Форма обладает
двумя полосами прокрутки: горизонтальной и вертикальной. Доступ к данным полосам осуществляется через свойства:
property HorzScrollBar: TControlScrollBar;
property VertScrollBar: TControlScrollBar;

За автоматическое включение полос прокрутки отвечает свойство формы AutoScroll. При установке его в true форма берет на себя обязательства в случае
необходимости автоматически включать вертикальную и горизонтальную
полосы прокрутки.
property AutoScroll : Boolean;

К ключевым свойствам полосы прокрутки стоит отнести свойства, влияющие на процесс перемещения клиентской части окна. И в первую очередь
это диапазон допустимых значений прокрутки (Range) и текущее положение
(Position) бегунка полосы:
property Range: Integer;
property Position: Integer;

214

Глава 9. Форма, интерфейсы SDI и MDI

Диапазон значений представляет собой виртуальный размер (в пикселах)
связанной с линией прокрутки клиентской части формы. Значения Range горизонтальной и вертикальной полос прокрутки должны превышать клиентский размер (ClientWidth и ClientHeight соответственно) формы. Положение
бегунка может принимать любое значение в пределах Range полосы. Следующий пример устанавливает бегунок горизонтальной полосы прокрутки в ее
центральной позиции.
with Form1 do
begin
ClientWidth:=300;
HorzScrollBar.Range:=ClientWidth*2;
HorzScrollBar.Position:=300;
end;

При каждом щелчке по кнопкам полосы прокрутки позиция бегунка получает приращение в соответствии со значением, заданным в свойстве Increment. По умолчанию значение шага составляет 8 пикселов.
type TScrollBarInc = 1..32767;
property Increment: TScrollBarInc;

Творческие натуры могут поэкспериментировать со свойствами, отвечающими за размеры и внешний вид полос прокрутки:
property Size: Integer;
//ширина полосы в пикселах
property ButtonSize: Integer;
//размер кнопок
property Style: TScrollBarStyle; //внешнее представление полос
TScrollBarStyle = (ssRegular, ssFlat, ssHotTrack);

Дополнительный интерес представляет свойство Margin, оказывающее специфическое влияние на вывод полосы прокрутки на экран:
property Margin: Word;

Каким образом? Мы привыкли к тому, что полоса прокрутки появляется
только в ситуации, когда элемент управления, скажем кнопка, не помещается в клиентской области окна. С помощью Margin можно задать отступ от края
оконного элемента управления до видимой границы формы, при котором будет показываться полоса прокрутки даже в том случае, если элемент управления полностью помещается в клиентской области. Поясню на примере:
procedure TForm1.FormCreate(Sender: TObject);
begin
with HorzScrollBar do
begin
Margin:=25;
Increment:=10;
Button1.Left:=0;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin

215

Форма – TForm
Button1.Left := Button1.Left + 25;
Form1.Caption := IntToStr(HorzScrollBar.Range);
end;

Обратите внимание, что значение свойства Margin назначается в момент создания формы.

Создание, показ и уничтожение формы
По умолчанию при старте программы все подключенные к проекту формы
создаются автоматически. Первой на свет появляется главная форма проекта, а затем все остальные согласно очередности, указанной программистом.
Для того чтобы изменить очередность создания форм, выберите
пункт меню Project → Options и на
вкладке Forms диалогового окна
Project Options (рис. 9.3) расставьте формы в списке Auto-create
forms в надлежащем порядке. Более того, реализована возможность отказа от автоматического
создания формы. Для этого достаточно перевести форму в разряд доступных, т. е. из левого
списка переместить ее в список
Available forms. Однако прежде
чем обращаться к такой форме,
программист должен ее создать.
Раз мы заговорили о создании
формы, то первое, что стоит обсудить, так это ее конструктор:

Рис. 9.3. Список форм, входящих в состав
проекта

constructor Create(AOwner: TComponent);

Вызов метода Create() полезен при создании форм, входящих в список Available forms. Допустим, что форма Form2 нашего проекта создается вручную. Тогда листинг программы будет выглядеть следующим образом:
var Form2:TForm2;
begin
if not Assigned(Form2) then // проверка на существование формы
begin
Form2:=TForm2.Create(Application);
Form2.Show;
// вывод формы на экран
end;
end;

При ссылке одного модуля на другой не забудьте добавить в строку uses имя нового
модуля. Другой вариант: выберите пункт меню File Ж Use unit (Alt+F11) и в списке
модулей найдите нужный модуль.

216

Глава 9. Форма, интерфейсы SDI и MDI

Сразу отметим существование альтернативного конструктора формы:
constructor CreateNew(AOwner: TComponent; Dummy: Integer = 0); virtual;

Основным достоинством конструктора CreateNew() считается возможность создать новый экземпляр формы без использования файла ресурсов формы .dfm.
Для отображения формы на экране монитора обычно применяют метод:
procedure Show;

Такой способ вывода формы на экран называется немодальным. В этом случае пользователь получает право свободно переключаться между окнами
приложения. Для вызова формы в модальном режиме используется метод:
function ShowModal: Integer;

Модальная форма отличается строптивым нравом. Она отображается поверх
всех открытых окон. Более того, пользователь не сможет получить доступ
к другим окнам проекта, пока не будет закрыта модальная форма.
Для передачи результата модальной форме на ее поверхности обычно размещают
кнопки класса TBitBtn (см. главу 8 «Стандартные компоненты»).

Закрываясь, модальная форма возвращает целочисленное значение, называемое модальным результатом. Это не что иное, как решение пользователя, нажавшего ту или иную кнопку.
type TModalResult = Low(Integer)..High(Integer);

Возможные значения модального результата представлены в табл. 9.2.
Таблица 9.2. Возможные значения TModalResult
Константа

Значение

Описание

mrNone

0

Значение по умолчанию – NONE.

mrOk

idOK

Пользователь нажал кнопку OK.

mrCancel

idCancel

Пользователь нажал кнопку CANCEL.

mrAbort

idAbort

Пользователь нажал кнопку ABORT.

mrRetry

idRetry

Пользователь нажал кнопку RETRY.

mrIgnore

idIgnore

Пользователь нажал кнопку IGNORE.

mrYes

idYes

Пользователь нажал кнопку YES.

mrNo

idNo

Пользователь нажал кнопку NO.

mrAll

MrNo + 1

Используется для определения последней константы.

Анализируя модальный результат закрывающейся формы, программист получает возможность направлять поведение программы в определенное русло:
if frmSelect.ShowModal=mrOK then
begin

Форма – TForm

217

{операторы для результата mrOK}
end else {операторы для результата,отличного от mrOK};

Результат модальной операции заносится в свойство формы:
property ModalResult: TModalResult;

По умолчанию это свойство установлено в 0 (константа mrNone). Присвоение
свойству ModalResult ненулевого значения приводит к вызову метода закрытия формы, и в качестве результата метода ShowModal() возвращается значение этого свойства.
Отмечу, что столь серьезный метод для показа формы, как ShowModal(), применим далеко не всегда. Например, в случаях, когда вы считаете возможным допустить пользователя к работе с несколькими формами одновременно, показ формы целесообразнее осуществлять при помощи метода Show().
Использование метода Show() является аналогом присвоения значения true
свойству Visible той же формы.
Мы обсудили создание и показ форм, а теперь рассмотрим методы их сокрытия и уничтожения. Простейшим способом спрятать (не уничтожая) форму
может стать присвоение свойству Visible значения false. Точно такого же результата можно достигнуть при помощи процедуры:
procedure Hide;

Для того чтобы уничтожить экземпляр формы максимально деликатно, рекомендую применять метод:
procedure Release;

Это ближайший соратник методов Free() и Destroy(), добивающийся тех же
результатов: он очищает память компьютера от бренных останков формы,
но, в отличие от последних, делает это с некоторым уважением – дожидается завершения обработки событий, инициированных этой формой или ее
компонентами.

Обработка событий
Как и любой оконный элемент управления, форма способна реагировать на
стандартные события системы. Кроме того, форма может похвастаться рядом своих собственных обработчиков событий, объявленных в основном
в TCustomForm.

От создания до уничтожения
Жизненный путь формы начинается в момент ее создания, когда инициируется событие:
property OnCreate: TNotifyEvent;

В табл. 9.3 приведена последовательность событий, возникающих при создании и выводе на экран новой формы.

218

Глава 9. Форма, интерфейсы SDI и MDI

Таблица 9.3. Основные события класса TForm
События

Описание

OnCreate()

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

OnShow()

Форма с секунды на секунду будет выведена на экран. Событие происходит после вызова метода Show() или ShowModal() или присвоения
свойству Visible формы значения true.

OnCanResize() Событие возникает перед изменением размера формы. Форма проверяет корректность своих новых размеров.
OnResize()

Событие инициируется сразу после изменения размеров формы.

OnActivate()

Форма становится активной.

OnPaint()

Осуществляется перерисовка формы.

Особый интерес представляет событие, возникающее при перерисовке формы:
property OnPaint: TNotifyEvent;

Используя этот обработчик события, программист может рисовать на поверхности формы.
В череде событий перерисовки всех размещенных на форме элементов управления
событие OnPaint() формы вызывается первым.
. . .
public
BitMap:TBitMap;
end;
var Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
BitMap:=TBitMap.Create;
BitMap.LoadFromFile('<Путь к растровому файлу .bmp>');
end;
procedure TForm1.FormPaint(Sender: TObject);
begin
Form1.Canvas.Draw(0, 0, BitMap);
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
BitMap.Free;
end;

219

Форма – TForm

Старайтесь не злоупотреблять возможностями события OnPaint() формы. Перерисовка окна происходит довольно часто, поэтому вывод на холст формы больших
изображений может значительно снизить производительность программы.

Не меньший интерес представляют события, возникающие при закрытии
формы. Для начала процесса закрытия формы необходимо вызвать метод:
procedure Close;

На рис. 9.4 представлена последовательность событий, возникающих у формы в процессе закрытия, а в табл. 9.4 приведено их краткое описание.
Таблица 9.4. События, сопровождающие процесс закрытия формы
События

Описание

OnCloseQuery()

Запрос разрешения на закрытие формы.

OnClose()

Форма закрывается.

OnDeactivate()

Форма перестает быть активной.

OnHide()

Форма скрывается с экрана.

OnDestroy()

Форма уничтожается.

Перед закрытием формы производится вызов обработчика события:
property OnCloseQuery: TCloseQueryEvent;
type TCloseQueryEvent = procedure(Sender: TObject; var CanClose: Boolean) of object;

Присвоение формальному параметру CanClose значения false отменяет закрытие формы. Ниже предложен пример, осуществляющий контроль за изменением текста в компоненте Memo1. Если текст модифицировался, то при
попытке закрытия формы отображается диалоговое окно, предлагающее сохранить изменения.
OnCloseQuery

false

CanClose

true

OnClose

Action
caNone
Без изменений

caHide

caMinimize

caFree

OnHide

OnMinimize

OnDestroy

Рис. 9.4. Алгоритм закрытия формы

220

Глава 9. Форма, интерфейсы SDI и MDI

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if Memo1.Modified=true then
begin
if MessageDlg('Сохранить изменения в тексте?', mtConfirmation,
[mbOk, mbCancel], 0) = mrOK then
begin
//сохранение изменений в тексте
end else CanClose := true;
end;
end;

Если форма получила разрешение на закрытие, далее генерируется событие:
property OnClose: TCloseEvent;
type TCloseAction = (caNone, caHide, caFree, caMinimize);
TCloseEvent = procedure(Sender: TObject; var Action: TCloseAction) of object;

Изменяя значение переменной Action (табл. 9.5), программист может управлять поведением закрываемой формы.
Таблица 9.5. Варианты закрытия формы TCloseAction
Значение

Описание

caNone

Ничего не происходит.

caHide

Форма не закрывается, а только становится невидимой. К данной
форме приложение имеет полный доступ. Значение по умолчанию
для SDI-форм.

caFree

Форма закрывается и освобождает занимаемые ресурсы.

caMinimize

Вместо закрытия форма сворачивается. Значение по умолчанию
для MDI-форм.

Реальное уничтожение формы и освобождение всех задействованных ею системных ресурсов произойдет только в случае, если присвоить переменной
Action значение caFree. Только тогда будет вызвано событие:
property OnDestroy: TNotifyEvent;

При этом форма и все принадлежащие ей объекты удаляются из памяти
компьютера.

Изменение размеров
Оба обработчика событий, связанные с изменением размеров формы, унаследованы от класса Tcontrol:
property OnCanResize: TCanResizeEvent;
type TCanResizeEvent = procedure(Sender: TObject; var NewWidth, NewHeight: Integer;
var Resize: Boolean) of object;

и
property OnResize: TNotifyEvent;

221

Форма – TForm

Событие OnCanResize возникает перед изменением размера формы, например
при растягивании границ формы мышью, при разворачивании/сворачивании формы или изменении размеров формы программным методом. Данный
обработчик события предоставляет возможность контролировать приемлемость новых размеров; он содержит четыре параметра (табл. 9.6).
Таблица 9.6. Параметры события OnCanResize()
Параметр

Описание

Sender

Элемент-источник сообщения.

NewWidth

Предлагаемый размер по горизонтали.

NewHeight

Предлагаемый размер по вертикали.

Resize

Переменная, разрешающая (true) или запрещающая (false) установить новый размер.

Допустим, что мы собираемся установить минимально допустимый размер
формы по горизонтали, равный 200 пикселам:
procedure TForm1.FormCanResize(Sender: TObject; var NewWidth, NewHeight: Integer;
var Resize: Boolean);
begin
Resize:=(NewWidth>=200); //false, если размер менее 200
end;

Событие OnResize() инициируется сразу после изменения размеров формы.
Как правило, событие используется для перераспределения компонентов,
расположенных на форме.

Быстрые клавиши
При изучении класса TWinControl мы встретились с тремя событиями клавиатуры: OnKeyDown(), OnKeyPress() и OnKeyUp(). У формы предусмотрено дополнительное событие, контролирующее нажатие пользователем быстрых клавиш:
property OnShortCut: TShortCutEvent;
TShortCutEvent = procedure (var Msg: TWMKey; var Handled: Boolean) of object;

В очередности событий, связанных с работой на клавиатуре, OnShortCut() вызывается самым первым и поэтому успевает обработать комбинацию быстрых
клавиш до того, как за дело берутся другие обработчики событий (как формы, так и элементов управления).
Для того чтобы не допустить дальнейшую обработку быстрых клавиш в последующей цепочке событий, установите параметр Handled в true, тем самым
вы укажете Windows, что обработка завершена. Параметр Msg соответствует
стандартному сообщению Windows для обработки нажатия клавиши на клавиатуре.
type
TWMKey = packed record
Msg : Cardinal;

222

Глава 9. Форма, интерфейсы SDI и MDI
CharCode
Unused
KeyData
Result

:
:
:
:

Word;
Word;
Longint;
Longint;

end;

Здесь параметр CharCode содержит код виртуальной клавиши (см. табл. 6.9
в главе 6). Параметр KeyData содержит дополнительную информацию: число
повторов данного сообщения, предыдущее состояние клавиши, состояние
дополнительных клавиш (таких как Alt, Ctrl). В поле Result будет записан результат обработки.
Простейший пример использования данного обработчика события приведен
ниже. Нажатие клавиши Esc (VK_ESCAPE) приведет к закрытию формы:
procedure TForm1.FormShortCut(var Msg: TWMKey; var Handled: Boolean);
begin
if Msg.CharCode=VK_ESCAPE then Close;
end;

Вместе с тем в более ранних версиях Delphi (по Delphi 4 включительно) программисты вместо события OnShortCut() с таким же успехом перехватывали
нажатия клавиш (прямо перед носом обескураженных элементов управления, находящихся в фокусе ввода) с помощью стандартных обработчиков событий. Но для этого необходимо было произвести ряд действий, и прежде
всего установить свойство:
property KeyPreview: Boolean;

в значение true. Тогда активная форма (окно) обладала приоритетом при обработке нажатия клавиш.
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if ssCtrl in Shift then {если нажата клавиша Ctrl}
case Key of
VK_UP
: Form1.Top:=Form1.Top-1;
VK_DOWN : Form1.Top:=Form1.Top+1;
VK_LEFT : Form1.Left:=Form1.Left-1;
VK_RIGHT : Form1.Left:=Form1.Left+1;
end;
end;

Приведенный пример демонстрирует, как можно научить форму перемещаться по экрану с помощью клавиш управления курсором.

Интерфейсы SDI и MDI
Операционная система Windows предоставляет возможность разрабатывать
приложения в одной из двух разновидностей интерфейса:
• SDI (Single Document Interface) – однодокументный интерфейс.
• MDI (Multiple Document Interface) – многодокументный интерфейс.

Интерфейсы SDI и MDI

223

Каждый из указанных стилей интерфейса обладает своими уникальными
возможностями и соответственно особенностями программирования. Характерными представителями интерфейса MDI являются большинство текстовых редакторов, электронных таблиц и программ работы с графикой, короче
говоря, приложений, способных одновременно работать с несколькими документами, открытыми в отдельных дочерних окнах. Все дочерние окна приложения MDI размещаются в клиентской области главного окна приложения.
Чтобы далеко не ходить за примером,
предлагаю взглянуть на утилиту Sysedit.exe, предназначенную для редактирования системных файлов (она
входит в стандартную комплектацию
Windows). Для этого в строке ввода
Открыть надо набрать команду Sysedit
(рис. 9.5) и нажать кнопку ОК.
В результате на экране монитора ото- Рис. 9.5. Стандартное окно запуска
бразится редактор файлов настрой- программы
ки, позволяющий опытному пользователю внести некоторые поправки в процесс инициализации Windows. Хочу предупредить, что без особой необходимости не стоит вносить изменения
в файлы настройки.
Как я и обещал, перед вами появилось
несколько дочерних окон, расположенных внутри главного окна (рис. 9.6). Вы
не сможете переместить дочерние окна
за пределы главного, и все управление
дочерними окнами осуществляется из
общего главного меню программы.
Приложение SDI также строится на базе главного и дочерних окон, но его дочерние окна имеют гораздо больше степеней свободы: они не столь крепко
привязаны к главной форме проекта
и могут свободно разгуливать по всему
пространству рабочего стола. В качестве примера классического приложения SDI может выступать Delphi. Глав- Рис. 9.6. Внешний вид приложения
ным окном Delphi служит форма, на с интерфейсом MDI
которой расположены главное меню,
палитра компонентов и «быстрые» кнопки. Остальные окна (окна проекта,
Инспектор объектов и др.) являются дочерними.

Построение проекта MDI
Для того чтобы сообщить Delphi о своем намерении построить приложение
с интерфейсом MDI, первым делом установите отвечающее за тип формы

224

Глава 9. Форма, интерфейсы SDI и MDI

свойство FormStyle главной формы проекта в значение fsMDIForm. Тем самым
вы назначите родительское окно приложения. Для всех остальных дочерних
форм проекта свойство FormStyle должно соответствовать значению fsMDIChild.
Если свойство FormStyle дочернего окна по-прежнему установлено в значение
fsNormal, то поведение дочернего окна будет соответствовать поведению окна
приложения SDI.
Вне зависимости от стиля интерфейса любое приложение обязано обладать главным окном. Все остальные окна являются дочерними. Главное окно определяется
в момент разработки приложения, и по умолчанию таковым становится самое первое окно – Form1. При желании назначить главным окном другую форму проекта надо воспользоваться пунктом меню Project → Options.

Одновременно с изучением особенностей приложения с интерфейсом MDI
в качестве примера работы мы попробуем написать упрощенный аналог утилиты Sysedit. Для этого создадим новый проект и в инспекторе объектов изменим имя и стиль главной формы:
Имя главной формы:
Стиль главной формы:

Name=frmMain
FormStyle= fsMDIForm

Добавим в проект новую форму. Для того чтобы сделать ее дочерней, изменим ее стиль и заодно переименуем:
Имя дочерней формы:
Стиль формы:

Name = frmChild
FormStyle = fsMDIChild

Выберите пункт меню File → Save для того, чтобы сохранить проект. В ответ
на запрос мастера сохранения укажите названия исходных файлов проектов. Модуль главной формы назовите main.pas, дочерней формы – child.pas.
Головной файл проекта предлагаю назвать mysysed.dpr. Теперь смело запустите проект на выполнение.
Обратите внимание на обещанную особенность приложения с интерфейсом
MDI: дочерняя форма может находиться только в пределах клиентской области родительского окна, и никакие усилия не смогут вынести ее за эти
границы. А вот еще один нюанс: попробуйте закрыть дочернюю форму. Получилось? Вместо того чтобы исчезнуть, дочерняя форма просто свернулась
и «упала» вниз главной формы. Почему? Перечитайте информацию о событии OnClose(): все определяется значением переменной Action : TCloseAction.
Для дочерней формы приложения MDI значение по умолчанию – caMinimize
(вместо уже привычного нам caHide).
Теперь для того чтобы, например, уничтожить дочернюю форму, ее обработчик события OnClose() должен выглядеть примерно так:
procedure TfrmChild.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;

Интерфейсы SDI и MDI

225

Для того чтобы при старте приложения избежать автоматического
создания дочерней формы, выберите пункт меню Project → Options
и в открывшемся диалоговом окне на вкладке Forms перенесите
форму frmChild из списка автоматически создаваемых форм в раздел доступных форм (рис. 9.7).
Каким образом можно создать
новую дочернюю форму? Все достаточно просто: вызываем из кода модуля главной формы конструктор дочерней формы:
unit main;

Рис. 9.7. Окно опций проекта
implementation
uses Child;

procedure …
var newChild : TFrmChild;
begin
newChild:=TFrmChild.Create(Application);
end;

Но не забудьте, что главная форма проекта (а точнее соответствующий ей
модуль main) нуждается в ссылке на дочернюю форму. Поэтому в начале раздела реализации модуля main.pas мы предупреждаем компилятор о том, что
намерены использовать модуль child.pas.
Вернемся к нашему примеру. Поместите на дочернюю форму многострочный текстовый редактор TMemo и его свойству Align присвойте значение alClient. В обработчике события OnShow главной формы проекта напишите следующие строки кода:
procedure TFrmMain.FormShow(Sender: TObject);
var newChild : TFrmChild;
begin
newChild:=TFrmChild.Create(Application);
NewChild.Memo1.Lines.LoadFromFile('c:\autoexec.bat');
NewChild.Caption:='Autoexec.bat';
newChild:=TFrmChild.Create(Application);
NewChild.Memo1.Lines.LoadFromFile('c:\config.sys');
NewChild.Caption:='Config.sys';
end;

Запустите проект. Мы научили приложение создавать два дочерних окна,
информирующих нас о содержимом файлов Autoexec.bat и Config.sys.

226

Глава 9. Форма, интерфейсы SDI и MDI

Ненадолго вернемся к теории. В классе TForm предусмотрен ряд свойств и методов, специализирующихся именно на приложениях с интерфейсом MDI.
Например, родительское окно приложения MDI всегда обладает информацией о количестве созданных дочерних окон:
property MDIChildCount: Integer;

Доступ ко всем созданным дочерним окнам можно получить из массива величин типа Tform:
property MDIChildren[I: Integer]: TForm;

Дочерние окна заносятся в массив в порядке их создания. Если какое-то
из окон в процессе работы приложения уничтожается, то массив самостоятельно упаковывается. Благодаря этим двум свойствам можно найти необходимое дочернее окно (или окна) и произвести с ним какие-то действия.
procedure …
var I : integer;
begin
for i:=0 to frmMain.MDIChildCount-1 do
if frmMain.MDIChildren[i].Caption='Новый документ'
then frmMain.MDIChildren[i].WindowState:=wsMinimized;
end;

Код приведенного листинга позволяет среди всех дочерних окон обнаружить
формы, в заголовке которых написано «Новый документ», и свернуть их.
Для того чтобы узнать, какое из дочерних окон в настоящий момент активно, достаточно обратиться к свойству:
property ActiveMDIChild: TForm;

Если у родительского окна в настоящий момент нет дочерних окон (свойство
MDIChildCount=0), свойство ActiveMDIChild вернет значение nil.
Ряд методов главного окна решает задачи, связанные с размещением и упорядочиванием дочерних окон в своей клиентской области. Для выравнивания значков свернутых дочерних окон предназначен метод:
procedure ArrangeIcons;

Эта команда разместит свернутые значки вдоль нижней рамки главной формы. Другой метод предназначен для размещения дочерних окон каскадом:
procedure Cascade;

Процедура позволяет упорядочить окна таким образом, что пользователь
сможет видеть панели заголовков всех дочерних окон и быстро найти нужное окно. Операционная система также позволяет разместить дочерние окна
таким образом, что они не будут перекрываться:
procedure Tile;

Метод Tile сотрудничает со свойством:

Приложение – класс TApplication

227

property TileMode: TTileMode;
type TTileMode = (tbHorizontal, tbVertical);

Код приведенного ниже примера упорядочит дочерние окна по горизонтали:
procedure …
begin
TileMode:= tbHorizontal;
Tile;
end;

Для перемещения по дочерним формам предназначены два метода:
procedure Next;
//активизировать следующую форму
procedure Previous; //активизировать предыдущую форму

Названия процедур говорят сами за себя: процедура Next() выбирает следующее окно, а Previous() возвращается к предыдущему. Напомню, что к текущему окну можно обратиться при помощи свойства ActiveMDIChild.

Интерфейс SDI
В противостоянии двух разновидностей интерфейса к сегодняшнему дню наметился существенный перевес приложений простого интерфейса SDI.
Взгляните на внешний вид такого серьезного программного продукта, как
Delphi. Это классический пример интерфейса SDI. Корпорация Microsoft
как законодатель мод также постепенно снижает долю приложений с многодокументным интерфейсом. Например, все приложения из пакетов Microsoft Office 2000 и XP работают в режиме SDI.
С точки зрения программирования модули SDI (что вполне естественно) требуют несколько иной организации работы приложения. В приложении SDI
главная форма проекта утрачивает некоторые возможности, например не
может упорядочивать свои дочерние окна на экране. Но главная отличительная черта проекта SDI в том, что все формы проекта (как главная, так
и подчиненные) создаются со стилем FormStyle = fsNormal. Благодаря этому
дочерние формы проекта могут свободно «бродить» за пределами клиентской области главной формы. В результате у пользователя создается впечатление, что все экранные окна приложения имеют равные права.
Выбор интерфейса будущего программного продукта – индивидуальное право программиста, определяемое его личными предпочтениями. Здесь трудно
дать однозначный совет. Скажу одно: на мой взгляд, интерфейс MDI целесообразно применять в проекте, предназначенном для одновременной работы
с несколькими однотипными документами. Интерфейс SDI более универсален и может использоваться во всех остальных проектах.

Приложение – класс TApplication
Любой работающий с формами проект состоит под незаметной, но тем не менее
очень жесткой опекой глобального объекта Application : Tapplication (рис. 9.8).

228

Глава 9. Форма, интерфейсы SDI и MDI

Попытка обнаружить объект Application среди обширного списка визуальных и невизуальных элементов
управления палитры компонентов Delphi обречена на
провал: такого компонента не существует. Названный
объект описан в модуле Forms и создается автоматически в самые первые мгновения после старта исполняемого модуля. Он выполняет роль посредника между
написанным в Delphi приложением и операционной
средой Windows, поэтому вполне справедливо утверждение, что объект Application инкапсулирует в себе
приложение.

TObject
TPersistent
TComponent
TApplication

Рис. 9.8. Иерархия
классов TAplication

К ключевым задачам класса TApplication относятся:
• подготовка и создание главного окна приложения;
• активизация приложения;
• перехват сообщений (messages) Windows;
• контроль за нажатием быстрых клавиш, соответствующих пунктам меню;
• вывод контекстной подсказки;
• обработка ошибок приложения.
Помимо этого TApplication спасает программиста от многих формальностей
Win32 API, связанных с объявлением и регистрацией оконных классов, описанием оконных процедур, циклов обработки сообщений и многого другого.
Программисты Borland скрывают от нас весьма существенную
часть свойств и методов Application. В некоторой степени они
правы, ведь львиную долю задач
по обеспечению работоспособности приложения класс TApplication способен решать самостоятельно, и вмешательство в его
функции со стороны начинающего программиста для проекта
может оказаться фатальным.
Вместе с тем Delphi разрешает
кое-что потрогать руками даже
«чайнику». Например, на стадии визуального проектирования можно назначить приложению новую пиктограмму, придуРис. 9.9. Настройка проекта с помощью
мать отображаемый на панели TApplication
задач заголовок и даже подключить файл справки. Для этого не нужно сочинять ни одной строки кода (хотя, безусловно, предусмотрен и такой способ).

229

Приложение – класс TApplication

В простейшем случае достаточно выбрать в меню Delphi пункт Project → Options… или нажать комбинацию клавиш Shift+Ctrl+F11. Откроется окно настройки проекта, в котором за доступ к приложению отвечает вкладка Application (рис. 9.9). Пусть скептически настроенный читатель не спешит расстраиваться, увидев такое «многообразие» возможностей по работе с классом.
Далее мы познакомимся и с более серьезными способами взаимодействия
с TApplication.
Имя исполняемого файла и полный путь к нему содержатся в свойстве:
property ExeName : string;

//только для чтения

Это свойство не раз сослужит хорошую службу, т. к. позволяет выяснить
месторасположение программы на дисках компьютера и «привязать» к ней
другие файлы, например журнал учета работы программы. Ниже представлен листинг, регистрирующий дату первого запуска программы.
procedure RegisteredApplication;
var FileName : string;
F : TextFile;
begin
FileName:=ExtractFilePath(Application.ExeName+'info.log');
if FileAge(FileName)=-1 then {если файла с именем FileName нет}
begin
{создаем файл и записываем в него дату первого старта}
AssignFile(F,FileName);
try
Rewrite(F);
WriteLn(F, FormatDateTime('"First start at" dd.mm.yy', Now));
finally
CloseFile(F);
end;
end;
end;

Для того чтобы идентифицировать основное окно приложения, воспользуйтесь свойством:
property MainForm: TForm;

Во время выполнения приложения можно обратиться к пиктограмме приложения:
property Icon: TIcon;

Заголовок свернутого приложения на панели задач Windows доступен
в свойстве:
property Title : string;

Для обеспечения взаимодействия с функциями Windows API зачастую требуются данные об указателе на главное окно приложения. Для этого используют функцию Windows API FindWindow(). Указатель на самое главное (невидимое) окно вашей программы хранится в свойстве:

230

Глава 9. Форма, интерфейсы SDI и MDI

property Handle: HWND;

Обратите внимание, что главное окно приложения не является синонимом
окна главной формы. Если проект использует диалоги, созданные при помощи функции Windows API CreateDialog(), а не диалоговые окна из набора
VCL Delphi, то понадобится свойство:
property DialogHandle: HWnd;

Если приложение активно, то свойство Active вернет значение true.
property Active: Boolean; // только для чтения

Создание и уничтожение приложения
При запуске исполняемого файла приложения (.exe) объект Application занят созданием и инициализацией форм приложения.
При старте приложения создаются только формы, включенные в состав автоматически создаваемых форм, перечисленных в списке Auto-create forms на вкладке
Forms диалогового окна Project Options. Последовательность создания этих форм
соответствует их очередности в этом списке.

Для инициализации приложения вызывается метод:
procedure Initalize;

Будьте внимательны: пока не созданы формы проекта, не может быть и речи
об изменении свойств объектов, расположенных на формах, или вызовах их
методов.
procedure CreateForm(FormClass: TFormClass; var Reference);

Метод CreateForm() присутствует в любом листинге стандартного проекта
(файл .dpr), конечно, при условии, что программа работает с формами. Первой всегда создается главная форма приложения. Данная процедура конструирует форму с типом, указанным в параметре FormClass. Формальный параметр Reference указывает на экземпляр формы, например Form1.
Ради справедливости надо отметить, что процесс создания приложения берет начало с вызова конструктора приложения:
constructor Create(AOwner: TComponent);

Но конструктор вызывается Delphi автоматически, и в большинстве случаев обычному программисту здесь делать нечего.

Вызов метода CreateForm() для создания новой формы допускается в любом
месте вашей программы. Метод очень похож на обычный Create(), за исключением того, что CreateForm() всегда проверяет, не равно ли свойство Application.MainForm значению nil.
program Project1;
uses Forms, Unit1 in 'Unit1.pas' {Form1};
{$R *.RES}

231

Приложение – класс TApplication
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.

// инициализация приложения
// создание формы
// запуск приложения

По завершении процесса создания форм объект Application вызывает метод
Run, запускающий приложение:
procedure Run;

Для любителей копаться в недрах приложения рекомендую изучить исходный код класса TApplication (модуль forms.pas). Кстати, огромный интерес
представляет процедура Run(), листинг которой приведен ниже:
procedure TApplication.Run;
begin
FRunning := True;
try
AddExitProc(DoneApplication);
if FMainForm <> nil then
begin
case CmdShow of
SW_SHOWMINNOACTIVE: FMainForm.FWindowState := wsMinimized;
SW_SHOWMAXIMIZED : FMainForm.WindowState := wsMaximized;
end;
if FShowMainForm then
if FMainForm.FWindowState = wsMinimized then
Minimize else
FMainForm.Visible := True;
repeat
HandleMessage
until Terminated;
end;
finally
FRunning := False;
end;
end;

Поле FRunning представляет собой глобальный флаг приложения, свидетельствующий о том, запущено (true) или нет (false) приложение. В случае возникновения исключительной ситуации (секция обработки ошибок try … finally … end) флаг очищается. Процедура AddExitProc() предназначена только
для обеспечения совместимости с более ранними версиями Delphi. Самое интересное происходит дальше. После проверки на существование главной формы проекта FMainForm <> nil метод Run определяет, в каком виде показывать
приложение (в минимизированном или развернутым на весь экран). Для этого в операторе-селекторе case … of проверяется состояние переменной CmdShow
и в поле FWindowState главной формы передается соответствующее значение.

232

Глава 9. Форма, интерфейсы SDI и MDI

На протяжении всей «жизни» приложения в цикле repeat … until объект Application вызывает метод HandleMessage, отвечающий за организацию цикла
обработки сообщений:
procedure HandleMessage;

Это классика для опытных программистов, работающих на Win32 API. Процедура HandleMessage прерывает выполнение приложения с тем, чтобы операционная система могла обработать одиночное сообщение из очереди сообщений Windows, перед тем как вернуть управление приложению. Если очередь
сообщений пуста, HandleMessage генерирует событие OnIdle() и отдает бразды
правления приложению.
К вопросам взаимодействия с Windows мы еще не раз вернемся. А процесс организации цикла обработки сообщений будет подробно рассмотрен в главе 27 «Программирование на Win32 API».

Приложение продолжает свою работу до тех пор, пока в его адрес не поступит сообщение WM_QUIT. В таком случае вызывается другой метод Application:
procedure Terminate;

Процедура вызывает функцию Windows API PostQuitMessage(). Приложение
уничтожает все созданные в процессе работы объекты и прекращает работу.
В экстренных случаях для немедленного останова приложения допустимо воспользоваться процедурой Halt(). Однако помните, что при этом не происходит корректного освобождения ресурсов, захваченных вашей программой у операционной системы.

При запуске приложения можно отказаться от показа главной формы. Для
этого установите в false свойство:
property ShowMainForm: Boolean;

Жизненный путь Application завершается вызовом деструктора. Как и в случае с Create(), он вызывается автоматически.
destructor Destroy;

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

233

Приложение – класс TApplication

Таблица 9.7. Организация контекстной подсказки
Свойство

Описание

property Hint: string;

В этом свойстве окажется показываемый в данный
момент текст подсказки.

property HintColor: TColor;

Цвет фона всплывающей подсказки.

property HintPause: Integer;

Интервал времени в миллисекундах, проходящий
с момента появления курсора мыши над компонентом до появления всплывающей подсказки.
По умолчанию – 500 миллисекунд.

property HintHidePause: Integer; Время демонстрации подсказки. По умолчанию –
2500 миллисекунд.
property HintShortPause: Integer; Время смены всплывающих подсказок при быстром перемещении курсора над компонентами.
По умолчанию – 50 миллисекунд.
property HintShortCuts: Boolean; Указывает, включать ли в подсказку информацию
о сочетаниях быстрых клавиш.

В момент вывода всплывающей подсказки у TApplication вызывается обработчик события:
property OnHint: TNotifyEvent;

Считаю нужным напомнить о возможности отображения подсказки не только в качестве всплывающего над компонентом текста, но и в строке состояния проекта. Дело в том, что свойство любого элемента управления Hint может содержать два варианта подсказки, разделенных символом «|» (вертикальная черта). Вот пример текста подсказки кнопки закрытия проекта:
btnClose.Hint := 'Выход|Завершение работы программы';

Способ организации вывода оперативной подсказки в строке состояния
представлен в следующем примере:
type TForm1 = class(TForm)
private
public
procedure LongTextHint(Sender: TObject);
end;
var Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.LongTextHint(Sender: TObject);
begin
StatusBar1.SimpleText := GetLongHint(Application.Hint);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin

234

Глава 9. Форма, интерфейсы SDI и MDI

Application.OnHint := LongTextHint;
end;

Теперь вторая часть подсказки отображается в строке состояния проекта –
компоненте StatusBar1.
В заключение упомянем процедуру, немедленно отменяющую вывод подсказки на экран:
procedure CancelHint;

Работа со справочной системой
Имя текущего файла помощи, сопоставленного с приложением, вы найдете
в свойстве:
property CurrentHelpFile: string; // только для чтения

Для взаимодействия со справочной системой Windows или с вашей справкой
предназначены методы:
function HelpCommand(Command: Word; Data: Longint): Boolean;
function HelpContext(Context: THelpContext): Boolean;
function HelpJump(const JumpID: string): Boolean;

Метод HelpCommand предназначен для вызова макросов, описанных в файле
справки. HelpContext и HelpJump выводят конкретную страницу справки.
Обращение приложения к файлу помощи можно проконтролировать в обработчике события OnHelp():
property OnHelp: THelpEvent;
type THelpEvent = function (Command: Word; Data: Longint; var CallHelp: Boolean):
Boolean of object;

Автоматический вызов этого события происходит в случае обращения
к функциям HelpCommand(), HelpJump() и HelpContext().

Сворачивание и восстановление приложения
За сворачивание приложения отвечает метод Minimize, а за восстановление
исходных размеров – Restore:
procedure Minimize;
procedure Restore;

Сворачивание/восстановление размеров приложения вызывает соответствующие обработчики событий:
property OnMinimize : TNotifyEvent;
property OnRestore : TNotifyEvent;

Особенности обработки событий в приложении и компонент TApplicationEvents

235

Особенности обработки событий в приложении
и компонент TApplicationEvents
Для организации доступа к событиям, на которые способно откликнуться
приложение, в Delphi (начиная с 5 версии) включен специальный невизуальный компонент TApplicationEvents (страница Additional палитры компонентов). У компонента практически нет никаких свойств, за исключением
имени Name и целочисленного значения Tag. Однако он сумел инкапсулировать все события (табл. 9.8), связанные с жизнедеятельностью приложения.
Таблица 9.8. Перечень событий TApplicationEvents
Событие

Описание

OnActionExecute() Событие возникает перед выполнением команды (объекта TAction).
OnActionUpdate()

Обработчик события используется для централизованного перевода элементов управления в актуальное состояние. Например,
в текстовом редакторе элемент управления «Сохранить» должен
быть активен только в том случае, если в приложении есть открытый документ.

OnActivate()

Событие возникает при получении приложением фокуса. Например, когда пользователь выбирает приложение на панели задач.

OnDeactivate()

Событие возникает в момент утраты приложением фокуса. Например, когда пользователь выбирает другое приложение.

OnException()

Приложение вызвало исключительную ситуацию. Обработка события подробно изложена в главе 15 «Обработка исключительных
ситуаций».

OnHint()

Показ оперативной подсказки.

OnIdle()

Приложение перешло в фоновый режим.

OnMessage()

Получение приложением сообщения от операционной системы
(см. главу 22 «Обмен данными между процессами»).

OnMinimize()

Приложение начинает сворачиваться.

OnRestore()

Приложение восстанавливается до исходных размеров.

OnShortCut()

Событие возникает при нажатии комбинации «быстрых» клавиш.
Обработчик события вызывается до события OnKeyDown().

OnShowHint()

Событие вызывается в момент отображения всплывающей подсказки.

Экран – класс TScreen
Задача класса TScreen заключается в отражении состояния экрана. Как и
в случае с TApplication, вы не найдете TScreen в списке визуальных компонентов. Экземпляр класса создается автоматически при запуске программы
и содержит информацию о характеристиках экрана, списке доступных
шрифтов и курсоров, о составе форм программы, активной форме приложе-

236

Глава 9. Форма, интерфейсы SDI и MDI

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

Информация об экране
Если компьютер сконфигурирован для одновременной работы с несколькими мониторами, то предлагаются два свойства:
property Monitors[Index: Integer]: TMonitor;
property MonitorCount: Integer;

Свойство Monitors предоставляет доступ к каждому из мониторов согласно их
индексу. Общее количество мониторов содержится в свойстве MonitorCount.
Таблица 9.9. Основные свойства объекта Screen
Свойство

Описание

property DesktopHeight: Integer; Высота, ширина и координаты левого верхнего угла рабочего стола Windows соответственно.
property DesktopWidth: Integer;
property DesktopLeft: Integer;
property DesktopTop: Integer;
property Height: Integer;

Высота экрана в пикселах.

property Width: Integer;

Ширина экрана в пикселах.

property PixelsPerInch: Integer; Количество пикселов в одном дюйме по вертикали.

Класс TScreen содержит информацию обо всех доступных курсорах системы:
property Cursors[Index: Integer]: HCursor;

Более того, используя свойство Cursors, программист сможет подключать
свои собственные курсоры. Для этого надо в редакторе ресурсов нарисовать
новый курсор, а в приложении объявить константу нового курсора (не совпадающую с константами уже существующих курсоров) при помощи функции Windows API LoadCursor():
Function LoadCursor(
INSTANCE : hInstance, // указатель на экземпляр приложения
CursorName : PChar;
// строка с именем курсора
) : HCursor;

На последнем этапе надо присвоить свойству Cursor значение константы нового курсора:
property Cursor: TCursor;

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

237

Экран – класс TScreen

функций программы выполняет достаточно большой объем вычислений
и поэтому «притормаживает» систему. Чтобы пользователь это понял, просто измените вид курсора на песочные часы на время работы ресурсоемкой
функции.
var crs : TCursor;
begin
crs:=Screen.Cursor;
Screen.Cursor:=crHourgLass;
MySuperHardFunction;
Screen.Cursor:=crs;
end;

//запомним курсор
//песочные часы
//действия программы
//возврат к предыдущему курсору

Информация о шрифтах системы
Список всех доступных шрифтов системы можно получить, обратившись
к свойству:
property Fonts: TStrings;

Так как шрифты хранятся в виде набора строк, то не представляет особого
труда передать набор любому компоненту, обладающему свойством Items
или Lines.
ComboBox1.Items := Screen.Fonts;

Для обновления списка шрифтов лучше всего подойдет метод:
procedure ResetFonts;

Для изменения шрифта, применяемого при выводе оперативной подсказки,
меню и имен файлов в стандартных диалоговых окнах открытия/сохранения файла, предназначены свойства:
property HintFont: TFont;
property MenuFont: TFont;
property IconFont: TFont;

Информация о формах проекта
Класс TScreen держит на контроле все отображаемые на экране монитора
формы. Для обращения к конкретной форме используйте свойство Forms,
указав индекс формы:
property Forms[Index: Integer]: TForm;

Общее количество форм содержится в свойстве:
property FormCount: Integer;

Для доступа к активной в данный момент форме и доступа к ее активному
элементу управления предназначены свойства:
property ActiveForm: TForm;
property ActiveControl: TWinControl;

238

Глава 9. Форма, интерфейсы SDI и MDI

Если приложение содержит модули данных, то обратиться к ним можно при
помощи свойства:
property DataModules[Index: Integer]: TDataModule;

Количество модулей в свойстве:
property DataModuleCount: Integer;

Вполне возможно, что проект вместо классических форм (экземпляров класса TForm) работает с потомками класса TCustomForm. В такой ситуации для доступа к формам стороннего разработчика понадобятся свойства:
property ActiveCustomForm: TCustomForm;
property CustomForms[Index: Integer]: TCustomForm;
property CustomFormCount: Integer;

Резюме
Практически ни одно VCL-приложение Delphi не обходится без компонента
TForm. Форма составляет основу пользовательского интерфейса; это одновременно и окно, и контейнер, способный не только содержать другие элементы
управления, но и управлять их поведением. Класс TForm вооружен богатым
списком обработчиков событий, позволяющих контролировать все ключевые этапы жизнедеятельности формы – от создания до уничтожения. Проект может состоять из нескольких форм. В этом случае различают главную
форму и дочерние формы приложения. Приложения такого рода строятся
на основе интерфейсов SDI или MDI.
Кроме того, в главе были рассмотрены глобальные объекты Application
и Screen, позволяющие управлять приложением и контролировать основные
характеристики экрана. Все события, на которые способно реагировать приложение, инкапсулированы в невизуальном элементе управления TApplicationEvents.

10
Графическая подсистема
Одним из наиболее значительных отличий ОС Windows от старой доброй
MS-DOS заслуженно считается визуальный интерфейс. Нельзя не отметить,
что первопроходцем в создании графической среды была отнюдь не компания Microsoft. Использующие графику прототипы разрабатывались еще
в 70-х годах (Xerox Palo Alto Research Center). Пальма первенства в разработке графической ОС принадлежит фирме Apple Computer, которая в январе 1983 года сообщила о создании ОС Liza. Microsoft заявила о работе над
Windows только в ноябре 1983 года, а первая версия, Windows 1.0, вышла
в свет спустя ровно два года. Между прочим, за этот период Apple уже успела анонсировать знаменитый Macintosh (январь 1984 г.).
Графический интерфейс пользователя (Graphical User Interface, GUI) Windows за более чем двадцать лет существования значительно расширился и был
улучшен. Основой GUI служит графический интерфейс устройства (Graphics
Device Interface, GDI). По большому счету GDI является языком графического программирования. Инженеры Microsoft добились того, что Windows абстрагирована от конкретного графического устройства, будь то дисплей, принтер, плоттер и т. п. Интерфейс GDI поддерживает аппаратно-независимую
графику, поэтому Windows требуется лишь драйвер конкретного устройства.
Графические функции 32-разрядной Windows в основном сосредоточены
в динамически подключаемой библиотеке GDI32.DLL. Кроме того, пока еще
используется 16-разрядная библиотека GDI.EXE. Свое нестандартное расширение она унаследовала еще от первых версий Windows. Эти библиотеки общаются с файлами драйверов графических устройств *.DRV. Что же умеет
GDI? Очень многое:
• Управлять выводом текста и шрифтами.
• Управлять цветом и палитрами.
• Работать с графическими примитивами (образами, путями, заливками
и т. д.).
• Отображать битовые образы (bitmaps, icons, cursors).

240



Глава 10. Графическая подсистема

Работать с метафайлами.
Взаимодействовать с графическими устройствами.

Разработчики Borland Delphi
провели огромную работу с целью упростить общение с GDI
(рис. 10.1). Первое, что заслуживает похвалы: программист
Delphi (в отличие от своих коллег, пишущих в среде Microsoft
Visual C++) освобожден от кропотливой работы, связанной
с получением и освобождением
контекста устройства. Для этого создан специальный класс
TCanvas (холст), инкапсулирующий в себе подавляющее большинство функций GDI и решающий проблемы с дескриптором
контекста устройства.
Создатели Delphi внедрили класс
TCanvas практически во все графические элементы управления,
что позволило использовать возможности деловой графики при
работе с такими компонентами.
Вместе с тем, если набора возможностей TCanvas для воплощения ваших художественных
фантазий недостаточно, возможна работа напрямую с методами Win32 API.

TObject
TPersistent
TGraphicsObject
TFont
TBrush
TPen
TCanvas
TGraphic

TPicture

TBitmap
TIcon
TMetafile
TJPEGImage

Рис. 10.1. Основные графические объекты VCL

В этой главе наряду с графическими функциями Delphi будут рассмотрены
и многие функции GDI. Такой подход позволит создать целостную картину
возможностей программирования деловой графики в Windows.

Представление цвета в Windows
Основной элемент монитора – электронная лучевая трубка. Трубка отображает все цвета спектра на основе сложения трех основных цветов: красного,
зеленого и синего (Red, Green и Blue). Модель воспроизведения цвета на основе сложения цветов называется аддитивной. Изменяя пропорции основных цветов, мы получаем все многообразие цветов.
Для представления цвета в Windows применяется 32-разрядное целое число
(LongWord). Младший байт числа содержит значение красного, второй байт –
зеленого и третий байт – синего цветов. Старший байт должен быть равен

241

Перо – класс TPen

нулю (рис. 10.2). Величина каждого из трех цветов может изменяться в пределах от 0 до 255, и чем больше значение, тем больше интенсивность соответствующего цвета. В терминах GDI тип данных для представления цвета называется COLORREF. В Delphi для описания цвета реализован аналог COLORREF –
тип данных TColor:
type TColor = -$7FFFFFFF-1..$7FFFFFFF;

Для удобства работы с «RGB-цветом» в Windows API предусмотрен ряд макросов. Макрос RGB() преобразует значения интенсивности трех цветов в определенный в Windows тип данных COLORREF (соответствующий двойному слову LongWord):

31

23
0x00

15

Синий

Зеленый

7

0

Красный

Рис. 10.2. Представление цвета
в виде двойного слова

function RGB(bRed, bGreen, bBlue : BYTE) : LongWord;

Для проверки макроса поместите на форму три компонента TTrackBar. Переименуйте их в tbRed, tbBlue и tbGreen соответственно. Свойствам Max всех компонентов назначьте значение 255. Затем опишите обработчик события OnChange() любого из элементов управления, как показано в примере:
procedure TForm1.tbRedChange(Sender: TObject);
begin
Form1.Color:=RGB(tbRed.Position,tbGreen.Position,tbBlue.Position);
Form1.Caption:=Format('Red=%d Green=%d Blue=%d',
[tbRed.Position,tbGreen.Position,tbBlue.Position]);
end;

В заключение сделайте этот обработчик события общим для всех трех ползунков TTrackBar и запустите новый проект. С изменением местоположения
ползунка будет изменяться цвет рабочей поверхности формы. Заголовок
формы отслеживает текущее значение всех составляющих RGB-цвета. Возможна и обратная операция: извлечение значений цветовых составляющих
из полного значения RGB-цвета:
function GetRValue(lwRGB: LongWord):Byte;
function GetGValue(lwRGB: LongWord):Byte;
function GetBValue(lwRGB: LongWord):Byte;

// красный
// зеленый
// синий

Цветам, наиболее часто применяемым в Delphi, соответствуют именованные
константы. Их несколько десятков. Например, черному цвету соответствует
константа clBlack, белому – clWhite. Кроме базовых цветов эти константы
способны описывать системные цвета – цвета, которыми будут отображаться рамки окон, тени, кнопки, текст. Например, цвет окна – clWindow, цвет поверхности кнопки – clButtonFace.

Перо – класс TPen
Класс TPen (Перо) применяется для определения характеристик линий, рисуемых на поверхности холста (любого элемента управления Delphi, обла-

242

Глава 10. Графическая подсистема

дающего свойством Canvas). Доступ к дескриптору пера осуществляется через свойство:
property Handle: HPen;

Цвет пера и толщина линии в пикселах определяются в свойствах:
property Color: TColor;
property Width: Integer;

В составе GDI предусмотрено всего три стиля пера: сплошное черное, сплошное белое и пустое перо. Собственное перо можно получить, создав собственное логическое перо при помощи функции Windows API (CreatePen() или
CreatePenIndent()). Программисты Borland как всегда пошли нам навстречу
и инкапсулировали в классе TPen свойство, определяющее стиль пера:
property Style: TPenStyle;
type TPenStyle = (psSolid, psDash, psDot, psDashDot, psDashDotDot, psClear,
psInsideFrame);

На рис. 10.3 представлены семь базовых стилей класса TPen, использование
которых (за исключением psSolid и psInsideFrame) имеет смысл только при
толщине пера не более 1 пиксела, в противном случае вы получите сплошную
линию. Стиль psClear соответствует пустому перу – рисование отключается.
Стоит особо отметить стиль psInsideFrame, кото- psSolid
рый обычно используется при рисовании простейpsDash
ших геометрических фигур пером шириной более
1 пиксела. Дело в том, что при использовании psDot
утолщенного пера (Style <> psInsideFrame) границы psDashDot
фигуры расширяются относительно заданных ко- psDashDotDot
ординат. Если вы нарисуете прямоугольник с ко- psClear
ординатами левого верхнего угла (10,10) и правого psInsideFrame
нижнего (100,100) пером psSolid толщиной 10 пикселов, действительный верхний угол окажется Рис. 10.3. Стили пера
в точке (1,1), а нижний – в точке (109,109). Границы фигуры расширятся ровно на ширину пера. Установка текущего пера
в состояние psInsideFrame запретит расширяться границам фигуры – линия
будет утолщена вовнутрь.
Поместите на форму комбинированный список TComboBox и заполните его
свойство Items названиями перьев в классе TPenStyle (psSolid, psDash и т. д.):
procedure TForm1.ComboBox1Change(Sender: TObject);
begin
with Form1.canvas do
begin
Repaint;
Pen.Width:=1;
case ComboBox1.ItemIndex of
0:Pen.Style:=psSolid;
1:Pen.Style:=psDash;
2:Pen.Style:=psDot;

243

Кисть – класс TBrush
3:Pen.Style:=psDashDot;
4:Pen.Style:=psDashDotDot;
5:Pen.Style:=psClear;
6:Pen.Style:=psInsideFrame;
end;
MoveTo(0,100); LineTo(100,100);
end;
end;

Наша небольшая подпрограмма проводит линию из точки с координатами
(0,100) в точку (100,100), стиль линии назначается в элементе управления
ComboBox1.
Режим прорисовки линии на холсте определяется свойством:
property Mode: TPenMode;
type TPenMode = (pmBlack, pmWhite, pmNop, pmNot, pmCopy, pmNotCopy, pmMergePenNot,
pmMaskPenNot, pmMergeNotPen, pmMaskNotPen, pmMerge, pmNotMerge, pmMask, pmNotMask,
pmXor, pmNotXor);

Представление цветной линии в таком случае определяется не только цветом пера, но и цветом области холста, по которой проходит линия. В Delphi
предложено 16 стандартных режимов взаимодействия цветов линии и холста. В терминах функций Windows API режимы смешивания цветов линии
и холста называются ROP2-кодами. ROP2-код обеспечивает поразрядную
логическую операцию (сложение, вычитание и т. д.) между пикселами пера
и пикселами поверхности, именуемую бинарной растровой операцией (binary raster operation).

Кисть – класс TBrush
Класс TBrush предназначен для
описания способа заливки областей и называется кистью. Кисть
представляет собой битовый образ. При закрашивании области
образ заполняет ее границы в вертикальном и горизонтальном направлениях.
Стандартные стили штриховки
(рис. 10.4) собраны в свойстве Style:

bsSolid

bsFDiagonal

bsHorizontal

bsCross

bsClear

bsBDiagonal

bsVertical

bsDiagCross

Рис. 10.4. Стандартные стили
штриховки кисти

property Style : TBrushStyle;
type TBrushStyle = (bsSolid, bsClear, bsHorizontal, bsVertical, bsFDiagonal,
bsBDiagonal, bsCross, bsDiagCross);

За цвет заливки отвечает свойство:
property Color: TColor;

Для создания собственной кисти необходимо загрузить битовый шаблон
в свойство:

244

Глава 10. Графическая подсистема

property Bitmap: TBitmap;

Приведем пример, в котором для заливки рабочей поверхности формы используется стандартный файл .bmp из каталога Windows:
var Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
Bitmap.LoadFromFile('c:\windows\Волны.bmp');
Form1.Canvas.Brush.Bitmap := Bitmap;
Form1.Canvas.FillRect(Rect(0,0,Form1.Width,Form1.Height));
Form1.Canvas.Brush.Bitmap := nil;
Bitmap.Free;
end;

Шрифт – класс TFont
Класс TFont описывает характеристики шрифта, используемые в Windows.
Все элементы управления из палитры компонентов Delphi, способные выводить текст, обладают свойством Font. Описание свойств класса шрифтов
представлено в табл. 10.1.
Таблица 10.1. Основные характеристики TFont
Свойство

Описание

property Charset: TFontCharset;

Номер набора символов шрифта. По умолчанию
соответствует DEFAULT_CHARSET. Русская кириллица – RUSSIAN_CHARSET.

property Color: TColor;

Цвет шрифта.

property FontAdapter: IChangeNoti- Автоматически информирует объекты ActiveX
fier;
о шрифте.
property Handle: HFont;

Дескриптор шрифта; применяется при работе
с GDI.

property Height: Integer;

Высота шрифта в пикселах.

property Name: TFontName;

Имя шрифта.

type TFontPitch
=
(fpDefault, Способ установки ширины символов: fpDefault –
fpVariable, fpFixed);
по умолчанию; fpFixed – у всех символов ширина
одинакова; fpVariable – переменная ширина.
property Pitch: TFontPitch;
property PixelsPerInch: Integer;

Число точек на дюйм; используется Windows
для установления соответствия между изображениями шрифта на экране и принтере.

property Size: Integer;

Высота шрифта в пунктах Windows.

Способ начертания шрифта: полужирный, курproperty Style: TFontStyles;
TFontStyle = (fsBold, fsItalic, сив, подчеркнутый и перечеркнутый.
fsUnderline, fsStrikeOut);
TFontStyles = set of TFontStyle;

245

Шрифт – класс TFont

Размер шрифта можно изменить при помощи Height или Size. Свойства связаны
формулой:
Font.Size = -Font.Height * 72 / Font.PixelsPerInch

Простейший способ изменения настроек шрифта в компоненте-метке Label1
представлен в следующем листинге:
with Label1.Font do
begin
Name:='Arial';
Size:=8;
Color:=clRed;
Style:=Font.Style-[fsBold];
end;

Приведенный ниже листинг демонстрирует способы сбора информации
о шрифтах системы в комбинированный список ComboBox1 и определяет вариант начертания каждого шрифта внутри элемента управления.
procedure TForm1.FormCreate(Sender: TObject);
begin
ComboBox1.Items:=Screen.Fonts; // сбор шрифтов системы
ComboBox1.ItemIndex:=0;
ComboBox1.style:=csOwnerDrawFixed;
end;
procedure TForm1.ComboBox1DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
var sRect :TRect;
const S ='АБВГД';
begin
with Combobox1.Canvas do
begin
sRect:=Rect; sRect.Right:=TextWidth(S);
Font.Name:=ComboBox1.Items.Strings[index];
TextRect(sRect, sRect.Left, sRect.Top, s);
Font.Name:='System';
TextOut(sRect.Right+10, Rect.Top, ComboBox1.Items.Strings[index]);
end;
end;

В работе со шрифтами можно добиться весьма впечатляющих результатов,
лишь немного покопавшись в недрах Win32 API. Примером может стать
структура LOGFONT, хранящая параметры шрифта:
tagLOGFONTA = packed record
lfHeight: Longint;
//
lfWidth: Longint;
//
lfEscapement: Longint; //
//
lfOrientation: Longint; //
//

высота символа в логических единицах
ширина символа в логических единицах
определяет угол поворота строки в десятых долях
градуса относительно оси X устройства
определяет угол поворота символа; в графическом
режиме GM_COMPATIBLE не играет роли

246

Глава 10. Графическая подсистема
lfWeight: Longint;
// определяет толщину линий шрифта от 0 до 900
lfItalic: Byte;
// ненулевое значение задает курсивное начертание
lfUnderline: Byte;
// ненулевое значение – подчеркивание
lfStrikeOut: Byte;
// ненулевое значение – перечеркивание
lfCharSet: Byte;
// указывает набор символов шрифта
lfOutPrecision: Byte;{определяет, в какой степени точность вывода должна
соответствовать заданным параметрам}
lfClipPrecision: Byte;{точность отсечения символов, выступающих
за пределы региона}
lfQuality: Byte;
// качество вывода шрифта
lfPitchAndFamily: Byte; // определяет шаг и семейство шрифта
lfFaceName: array[0..LF_FACESIZE - 1] of AnsiChar; // имя шрифта
end;

Предлагаемый листинг осуществляет поворот текстовой строки на 90 градусов:
var LogFont: TLOGFONT;
begin
GetObject(Form1.Canvas.Font.Handle, SizeOf(LogFont), Addr(LogFont));
LogFont.lfFaceName:=('Arial'+#0);
LogFont.lfEscapement := 90*10; // поворот на 90 градусов
Form1.Canvas.Font.Handle := CreateFontIndirect(LogFont);
Form1.Canvas.TextOut(100,100,'АБВГДЕЕ...');
end;

Основные функции выполняет метод CreateFontIndirect(). Он создает логический шрифт в соответствии с характеристиками, заложенными в структуру TLOGFONT, и устанавливает его текущим для холста рабочей формы.
function CreateFontIndirect(LogFont: TLOGFONT) : HFONT;

Обратите внимание, что в примере используется шрифт Arial, а не шрифт проекта
по умолчанию. Причина проcта: повернуть можно только шрифты True Type.

Холст – класс TCanvas
Класс TCanvas связывает воедино дескриптор контекста устройства GDI и набор функций GDI. По сути, TCanvas является надстройкой над GDI Windows.
В классе инкапсулированы уже знакомые нам TBrush, TPen и TFont:
property Brush: TBrush;
property Pen: TPen;
property Font: TFont;

Дескриптор графического устройства находится в свойстве:
property Handle: HDC;

Холст отличается богатейшим набором методов для рисования, но самый
простейший из них прячется в свойстве:
property Pixels[X, Y: Integer]: TColor;

Холст – класс TCanvas

247

Благодаря свойству Pixels мы получим доступ к любой точке холста по ее координатам X и Y. При этом можно решить два вопроса: во-первых, узнать, какой цвет установлен для этой точки, и, во-вторых, самим назначить указанной точке требуемый цвет:
if Form1.Canvas.Pixels[9,9]=clBlack then Form1.Canvas.Pixels[9,9]:=clRed;

Текущая позиция пера:
property PenPos : TPoint;

Прямоугольник, ограничивающий область, предназначенную для рисования, доступен (только для чтения) в свойстве:
property ClipRect : TRect;

Способ наложения изображения на холст определяется свойством:
property CopyMode: TCopyMode default cmSrcCopy;
type TCopyMode = Longint;

Класс TCopyMode представляет собой константы кодов растровых операций.
Класс TCanvas может похвастаться двумя незначительно усовершенствованными
потомками: TControlCanvas и TMetafileCanvas. Первый из них инкапсулируется
в ряде элементов управления, допускающих операции прорисовки на своей поверхности. К таковым элементам относятся потомки TCustomForm, TGraphicControl,
TCustomCombo и т. д. В свою очередь TMetafileCanvas специализируется на работе
с метафайлами – векторными изображениями.

Вывод текста
Для вывода текста в заданных точках чаще всего используется метод:
procedure TextOut(X, Y: Integer; const Text: string);

Параметрами X и Y задается начальная позиция строки в логических координатах. В эту точку помещается левый верхний угол первого символа выводимой строки. Большими возможностями обладает метод:
procedure TextRect(Rect: TRect; X, Y: Integer; const Text: string);

Параметр Rect задает границы выводимого текста. Все, что не поместилось
в пределах прямоугольника Rect, останется невидимым.
var Rct: TRect;
begin
Rct.Left:=10; Rct.Top:=10; Rct.Right:=58; Rct.Bottom:=25;
Form1.Canvas.TextRect(Rct,10,10,'Функция TextRect');
end;

Совместно с TextRect() часто используют функции, возвращающие высоту
и ширину строки в пикселах для текущего шрифта.
function TextHeight(const Text: string): Integer;
function TextWidth(const Text: string): Integer;

248

Глава 10. Графическая подсистема

Функция TextExtent() вполне может заменить оба предыдущих метода:
function TextExtent(const Text: string): TSize;
type TSize = record
cx: Longint;
cy: Longint;
end;

С особенностями вывода текста на графическое устройство тесно связано
свойство холста:
property TextFlags: LongInt;

В свойстве инкапсулированы параметры функции Win32 API ExtTextOut().
Возможные значения флагов приведены в табл. 10.2.
Таблица 10.2. Основные флаги для свойства TextFlags
Константа

Описание

ETO_CLIPPED

Флаг автоматически устанавливается при вызове метода TextRect().
Строка выводится в границах заданного прямоугольника Rect.

ETO_OPAQUE

Строка выводится с непрозрачным фоном.

ETO_RTLREADING

Флаг предназначен для работы с ближневосточной письменностью
(справа налево).

ETO_GLYPH_INDEX Текст выводится непосредственно средствами GDI без анализа языковой принадлежности; обычно используется совместно со шрифтами TrueType.

Значительно больше возможностей по выводу текста предоставляет функция Windows API:
function DrawText( HDC : hDC, // дескриптор контекста
Text : pChar,
// содержимое текста
Count: Integer,
// количество символов
Rect : pRect,
// прямоугольная область для вывода текста
dtFormat : Cardinal
// опции форматирования текста
): Integer;

По сравнению с простейшим методом TextOut() наша новая знакомая не допустит выхода текста за пределы границы прямоугольной области Rect. Параметр Count требует указать количество выводимых символов, но если вы
передадите значение –1, то функция самостоятельно найдет нулевой символ,
завершающий строку, и выведет ее всю. Параметр dtFormat определяет опции
форматирования текста; некоторые его значения приведены в табл. 10.3.
Таблица 10.3. Опции форматирования текста метода DrawText()
Константа

Описание

DT_BOTTOM

Выравнивание текста по нижней границе прямоугольника Rect.

DT_TOP

Выравнивание по верхней границе.

DT_RIGHT

Выравнивание вправо.

Холст – класс TCanvas

Константа

Описание

DT_LEFT

Выравнивание влево.

DT_VCENTER

Центрирование по вертикали.

DT_CENTER

Центрирование по горизонтали.

249

В случае успеха функция DrawText() возвращает высоту текста в пикселах,
а при неудаче – ноль.
var Rect : TRect;
S : string;
begin
Rect.Left:=10; Rect.Top:=10;
Rect.Bottom:=Rect.Left+20;
Rect.Right:=100;
s:='ABCDEF'+#0;
DrawText(Form1.Canvas.Handle, pChar(s), -1, Rect, DT_CENTER);
end;

Построение отрезков
Для того чтобы нарисовать любое изображение, достаточно присвоить нужным пикселам необходимый цвет (свойство холста Pixels), и в результате получить желаемый шедевр. Но это чрезмерно трудоемкий процесс, и, надеюсь, вы им никогда не воспользуетесь. Тем более, что в классе TCanvas инкапсулирован достаточно обширный набор методов рисования линий и простейших геометрических фигур.
Для того чтобы нарисовать простейшую прямую, воспользуйтесь двумя методами холста:
procedure MoveTo(X, Y: Integer);
procedure LineTo(X, Y: Integer);

Процедура MoveTo() позиционирует перо на начальную точку отрезка. Метод
LineTo() рисует отрезок из текущей точки до конечной (причем конечная
точка не закрашивается) и оставляет перо в этой точке. В следующем примере при помощи этих методов поверхность формы покрывается вертикальными линиями с шагом, равным 50 пикселам.
var X:integer;
begin
X:=50;
while X<Form1.Width do
begin
Form1.Canvas.MoveTo(X,0);
Form1.Canvas.LineTo(X,Form1.Height);
X:=X+50;
end;
end;

250

Глава 10. Графическая подсистема

Помимо прямых отрезков арсенал методов холста обеспечивает построение
ломаных линий:
procedure Polyline(Points: array of TPoint);

Для рисования ломаной необходимо заполнить массив координат логических точек, через которые пройдет линия. Начальной и конечной точкам ломаной будут соответствовать первый и последний элементы массива.
Form1.Canvas.Polyline([Point(10,10),Point(20,10),Point(40,50),Point(40,0)]);

Ближайший родственник ломаной линии – многоугольник:
procedure Polygon(Points: array of TPoint);

Отличие этого метода от процедуры Polyline() заключается в том, что последняя объявленная в массиве точка соединяется с первой и область внутри
многоугольника закрашивается текущей кистью.
procedure Polygon(Points: array of TPoint);
var Arr : array [0..99] of TPoint;
begin
Form1.Canvas.Brush.Style:=bsHorizontal;
Form1.Canvas.Brush.Color:=clRed;
Arr[0]:=Point(10,10); Arr[1]:=Point(20,10);
. . .
Arr[99]:=Point(100,100);
Form1.Canvas.Polygon(Arr);
end;

При желании вывести на экран не весь массив точек, а только его часть, воспользуйтесь функцией Slice(). Второй параметр функции ограничивает верхнюю границу массива:
Form1.Canvas.Polygon(Slice(Arr, 10));

Простейшие геометрические фигуры
Для рисования прямоугольника можно
воспользоваться методом Rectangle(). Процедура является перегружаемой и в качестве параметров принимает координаты
верхнего левого (X1, Y1) и нижнего правого (X2, Y2) углов прямоугольника или координаты, заданные в формате TRect
(рис. 10.5):
procedure Rectangle(X1, Y1, X2, Y2:
Integer); overload;
procedure Rectangle(const Rect:
TRect); overload;

X

0,0
X1, Y1

Y

X2, Y2

Рис. 10.5. Определение координат
для метода Rectangle()

Прямоугольник со скругленными углами получится при использовании метода:
procedure RoundRect(X1, Y1, X2, Y2, X3, Y3: Integer);

251

Холст – класс TCanvas

Первые четыре параметра соответствуют координатам вершин функции Rectangle(). Значения X3 и Y3 определяют степень сглаживания углов, задавая
ширину и высоту скругляющего эллипса (рис. 10.6).
X

0,0
X1, Y1

Y3

X

0,0
X1, Y1

X3

X2, Y2

Y

X2, Y2

Y

Рис. 10.6. Определение координат
для метода RoundRect()

Рис. 10.7. Определение координат
для метода Ellipse()

Вы уже заметили, что большинство методов GDI рисуют простейшие геометрические фигуры внутри ограничивающей прямоугольной области. Например, эллипс (рис. 10.7) вписывается в рамки прямоугольника с координатами, аналогичными процедуре Rectangle():
procedure Ellipse(X1, Y1, X2, Y2: Integer); overload;
procedure Ellipse(const Rect: TRect); overload;

Следующие три родственных метода посвящены прорисовке части эллипса:
procedure Arc(X1, Y1, X2, Y2, X3, Y3, X4, Y4 : Integer);
procedure Chord(X1, Y1, X2, Y2, X3, Y3, X4, Y4: Integer);
procedure Pie(X1, Y1, X2, Y2, X3, Y3, X4, Y4 : Longint);

Процедура Arc() чертит сегмент, Chord() – хорду, а Pie() – сектор эллипса
(рис. 10.8). Как вы уже догадались, все изображения вписываются в эллипс
(а точнее в прямоугольник, содержащий эллипс) с координатами вершин
(X1, Y1) и (X2, Y2). Логические координаты (X3, Y3) и (X4, Y4) описывают
воображаемые лучи, исходящие из центра эллипса. Места пересечения лучей с контуром эллипса соединяются дугой. Начальной точкой дуги считается пересечение эллипса с лучом (X3, Y3), конечной – с лучом (X4, Y4).
X

0,0
X1, Y1

Y

Начальная точка
(X3, Y3)

Конечная точка
(X4, Y4)

X1, Y1

X2, Y2

Рис. 10.8. а) метод Arc()

X

0,0

Y

Начальная точка
(X3, Y3)

Конечная точка
(X4, Y4)

б) метод Chord()

X2, Y2

X

0,0
X1, Y1

Y

Начальная точка
(X3, Y3)

Конечная точка
(X4, Y4)

в) метод Pie()

X2, Y2

252

Глава 10. Графическая подсистема

Для рисования сегмента окружности (рис. 10.9) пользователи Windows NT
могут воспользоваться функцией Windows API AngleArc() (к сожалению, эта
функция не поддерживается в Windows 95/98/Me):
function AngleArc(HDC: hdc; X,Y: integer; dwRadius: Longword;
eStartAngle, eSweepAngle : real):boolean;

В качестве параметров функции AngleArc()
выступают: HDC – дескриптор контекста устройства; X и Y – координаты центра окружности; dwRadius – радиус окружности в пикселах; eStartAngle – угол (в градусах) относительно оси Х, определяющий начальную
точку сектора; eSweepAngle – угол раскрытия
сектора.
Методы Rectangle(), Ellipse(), RoundRect(),
Chord() и Pie() предназначены не только
для рисования простейших геометрических
фигур, но и для закрашивания текущей кистью ограниченной этими фигурами области. По умолчанию кисть сплошная и белая.

X

0,0
eSweepAngle

eStartAngle

X, Y

dwRadius
Y

Рис. 10.9. Сегмент окружности
AngleArc()

Методы для работы с областью
Холст поддерживает несколько методов рисования, использующих структуру TRect. Как вы помните, TRect представляет собой запись, описывающую координаты верхнего левого и правого нижнего углов прямоугольной области:
type TRect = record case Integer of
1: (TopLeft, BottomRight: TPoint);end;

0: (Left, Top, Right, Bottom: Integer);

Обычное копирование прямоугольника Source с холста Canvas осуществляет
процедура:
procedure CopyRect(Dest: TRect; Canvas: TCanvas; Source: TRect);

Результат копирования помещается в прямоугольник Dest. Изменяя размерность прямоугольника-получателя, можно получить эффект увеличения
или уменьшения изображения. Скопировать прямоугольник Source из битовой карты Bitmap в прямоугольник Dest поможет процедура:
procedure BrushCopy(const Dest: TRect; Bitmap: TBitmap; const Source: TRect; Color:
TColor);

Особенность метода в том, что при копировании цвет Color заменяется на текущий цвет кисти. Этот нюанс позволяет рисовать прозрачную картинку.
Для этого достаточно заменить фоновый цвет копируемого изображения
на фоновый цвет холста.
var Bitmap: TBitmap; Rect1, Rect2: TRect; sFileName: string;
begin
sFileName:= . . .;

Холст – класс TCanvas

253

Rect1 := Rect(0,0,100,100); Rect2 := Rect(110,0,310, 200);
Bitmap := TBitmap.Create;
Bitmap.LoadFromFile(sFileName);
Form1.Canvas.Brush.Color:=Form1.color;
Form1.Canvas.BrushCopy(Rect1, Bitmap, Rect1, clWhite);
Form1.Canvas.CopyRect(Rect2,Bitmap.Canvas,Rect1);
Bitmap.Free;
end;

В листинге продемонстрирован пример использования функций BrushCopy()
и CopyRect(). Метод BrushCopy() размещает на холсте растровую картинку
из файла sFileName, причем фоновый цвет Bitmap замещается фоновым цветом кисти холста. Метод CopyRect() копирует изображение в прямоугольник
вдвое большего размера, чем достигается эффект увеличения.
Способ наложения изображения на холст определяется свойством CopyMode. По умолчанию режим копирования «один в один» – cmSrcCopy.

Заливку области Rect текущей кистью осуществляет метод:
procedure FillRect(const Rect: TRect);

Контур толщиной 1 пиксел для области Rect нарисует процедура FrameRect(),
причем оконтуривание осуществляется текущей кистью:
procedure FrameRect(const Rect: TRect);

Эффект наличия фокуса ввода на прямоугольнике Rect обеспечивает метод:
procedure DrawFocusRect(const Rect: TRect);

При повторном вызове процедуры фокус ввода снимается, т. е. изображение
приводится к первоначальному виду.
Заливка текущей кистью любой области (необязательно прямоугольной)
осуществляется методом:
procedure FloodFill(X, Y: Integer; Color: TColor; FillStyle: TFillStyle);
type TFillStyle = (fsSurface, fsBorder);

Заливка начинается с точки (X,Y). Режим заливки определяется значением
параметра FillStyle. Если параметр принимает значение fsSurface, заливка
прекратится только тогда, когда по соседству с точкой (X,Y) не останется ни
одного пиксела с цветом Color. Второй режим (fsBorder) заставляет метод вести себя иначе. Процедура станет закрашивать окрестности точки (X,Y) до тех
пор, пока не выйдет на границу с цветом Color. Приведенный ниже листинг
демонстрирует способ закрашивания части окружности:
with Form1.Canvas do
begin
Ellipse(10,10,100,100);
MoveTo(10,10);
LineTo(100,100);
Brush.Color:=clRed;
FloodFill(30, 55, clBlack, fsBorder);
end;

254

Глава 10. Графическая подсистема

Кривые Безье
Кривые (сплайны) Безье названы в честь
французского математика Пьера Безье,
предложившего в 60-х годах XX века автомобилестроительной фирме Renault
набор математических выражений, позволяющих проектировать кузова автомобилей с использованием вычислительной техники.

X

0,0
2
Отклоняющие
точки

4
1
3

Для задания простейшего сплайна БеКонечные
зье достаточно четырех точек. Две точточки
Y
ки несут привычную нагрузку: задают
начальные и конечные координаты Рис. 10.10. Кубический сплайн Безье
кривой. Изюминка заключается в роли
оставшихся точек: они оттягивают кривую от прямой, соединяющей две
крайние точки. От расположения отклоняющих точек зависит, где и каким
образом кривая сменит направление. Для построения кривой Безье (кубического сплайна) воспользуйтесь методом:
procedure PolyBezier(const Points: array of TPoint);

Для задания первого сплайна необходимо 4 точки. Каждому последующему
сплайну достаточно 3-х новых точек, поскольку начальная точка новой кривой есть конечная точка предыдущей. Координатами конечных точек первой кривой служат первый и четвертый элементы массива Points. В качестве
точек смещения выступают второй и третий элементы массива.
var pArr : array [1..4] of TPoint;
begin
// 1 и 4 элементы массива - конечные точки кривой
pArr[1]:=Point(10,100); pArr[4]:=Point(200,100);
// 2 и 3 - отклоняющие точки
pArr[2]:=Point(100,0); pArr[3]:=Point(400,200);
Form1.Canvas.PolyBezier(pArr);
end;

В арсенале холста есть еще один метод, строящий кубический сплайн:
procedure PolyBezierTo(const Points: array of TPoint);

Единственное отличие от процедуры PolyBezier() заключается в том, что
в качестве первой точки кривой Безье используется текущая позиция пера,
следовательно, количество элементов массива должно быть кратно трем.

Класс TGraphic
Назначение класса TGraphic – поддержка основных графических форматов
Windows. TGraphic является основополагающим абстрактным классом для
таких графических объектов, как растровые изображения, пиктограммы

Класс TGraphic

255

и метафайлы, а также для разновидности растровых изображений – сжатой
растровой картинки (формат JPEG). Перечисленные графические объекты
наследуют от TGraphic основные свойства и методы, обеспечивающие загрузку и сохранение изображений, установку размеров и некоторые другие.
Свойство Empty принимает значение true, если графический объект не содержит данных.
property Empty: Boolean;

Свойство Modified показывает, изменялся ли данный графический объект.
Свойство используется совместно с обработчиком события OnChange().
property Modified: Boolean;

Состояние цветовой палитры графического изображения определяется свойством Palette. Если палитра не используется изображением, значение свойства равно 0.
property Palette: HPALETTE;

При изменении цветовой палитры изменяется состояние свойства:
property PaletteModified: Boolean;

Свойство используется совместно с обработчиком события OnChange().
За прозрачность графического объекта отвечает свойство:
property Transparent: Boolean;

Максимальные размеры графического объекта по вертикали и горизонтали:
property Height: Integer;
property Width: Integer;

Класс TGraphic поддерживает операции по работе с потоком, файлом и буфером обмена Windows. Нижеприведенные методы осуществляют загрузку
или сохранение графических объектов.
В поток:
procedure LoadFromStream(Stream: TStream);
procedure SaveToStream(Stream: TStream);

В файл:
procedure LoadFromFile(const FileName: string);
procedure SaveToFile(const FileName: string);

В буфер обмена Windows:
procedure LoadFromClipboardFormat(AFormat: Word; AData: THandle; APalette: HPALETTE);
procedure SaveToClipboardFormat(var AFormat: Word; var AData: THandle; var APalette:
HPALETTE);

В качестве параметров при работе с буфером обмена выступают: AFormat – зарегистрированный графический формат буфера обмена Windows (см. главу 22

256

Глава 10. Графическая подсистема

«Обмен данными между процессами»), AData – указатель на данные, APalette –
указатель на цветовую палитру.
Для полиморфного присваивания графических объектов воспользуйтесь методом:
procedure Assign(Source: TPersistent);

Например:
Bitmap2.Assign(Bitmap1);

Загрузка графических объектов больших размеров занимает много времени.
В классе TGraphic предусмотрена возможность информировать пользователя
о процессе загрузки данных благодаря использованию события:
property OnProgress: TProgressEvent;
TProgressEvent = procedure (Sender: TObject; Stage: TProgressStage; PercentDone:
Byte; RedrawNow: Boolean; const Rct: TRect; const Msg: string) of object;
type TProgressStage = (psStarting, psRunning, psEnding);

Параметры обработчика события описаны в табл. 10.4.
Таблица 10.4. Параметры события TProgressEvent()
Параметр

Описание

Sender

Элемент-источник сообщения.

Stage

Сигнализирует о стадии процесса: psStarting – начало процесса,
psRunning – процесс в работе, psEnding – окончание процесса.

PercentDone

Процент выполненной работы.

RedrawNow

Состояние прорисовки изображения.

Rct

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

Msg

Может содержать текстовое сообщение, описывающее текущую
операцию.

procedure TForm1.Image1Progress(Sender: TObject; Stage: TProgressStage;
PercentDone: Byte; RedrawNow: Boolean; const R: TRect; const Msg: string);
begin
case Stage of
psStarting: frmProgress.Show;
psRunning : frmProgress.ProgressBar1.Position:=PercentDone;
psEnding : frmProgress.Close;
end;
end;

В листинге приведен пример использования события OnProgress(). Состоянием
параметра Stage определяется поведение формы frmProgress (показ/закрытие).
Не все графические объекты способны использовать обработчик события OnProgress(). Такой возможностью обладает сжатая растровая картинка (формат JPEG).

Пиктограмма – класс TIcon

257

Взаимодействие классов TCanvas и TGraphic
В наборе методов холста предусмотрен метод для вывода графических изображений:
procedure Draw(X, Y: Integer; Graphic: TGraphic);

Процедура Draw() вызывает метод прорисовки графического объекта. Левый
верхний угол объекта выводится в точке (X,Y).
Масштабирование объекта класса TGraphic обеспечивает процедура:
procedure StretchDraw(const Rect: TRect; Graphic: TGraphic );

Метод StretchDraw() рисует объект TGraphic в границах прямоугольной области Rect. Если размер исходного графического объекта не совпадает с размерами прямоугольника, Graphic масштабируется – вписывается в прямоугольную область.

Пиктограмма – класс TIcon
Класс TIcon инкапсулирует пиктограмму Windows. Объект-пиктограмма загружается из файла .ICO. Набор свойств и методов класса невелик. Геометрические размеры пиктограммы жестко заданы глобальными переменными
Windows, поэтому свойства Height и Width используйте в режиме только для
чтения:
property Height: Integer;
property Width: Integer;

Пиктограмма всегда прозрачна, т. е. свойство Transparent всегда равно true.
property Transparent: Boolean;

Доступ к объекту TIcon осуществляется через дескриптор:
property Handle: HIcon;

Метод ReleaseHandle передает дескриптор пиктограммы другому объекту, обнуляя ссылку на него:
function ReleaseHandle: HIcon;

Класс TIcon как наследник TGraphic поддерживает операции по работе с потоком и файлом, но не воспринимает буфер обмена Windows.
В Windows API предложены функции, позволяющие извлекать пиктограммы, ассоциированные с приложением. Одна из наиболее распространенных
функций:
function ExtractAssociatedIcon(HINSTANCE:hInst; Path : PChar; lpiIcon :
word):HIcon;

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

258

Глава 10. Графическая подсистема

пиктограммы. Ниже приведен листинг с примером использования функции
ExtractAssociatedIcon(). При желании применить пример на практике не забудьте подключить к вашему проекту модуль ShellAPI.
implementation
uses ShellAPI;
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var Icn : TIcon; i : word; fName : string;
begin
try
Icn:=TIcon.create;
fName:=Edit1.text; // полное имя файла
Icn.Handle:=ExtractAssociatedIcon(application.handle, PChar(fName), i);
Form1.canvas.draw(10,10,Icn);
finally
Icn.free;
end;
end;

Растровое изображение – класс TBitmap
В Windows предусмотрены две основные формы хранения изображений:
растровая (bitmap) и векторная (metafile). Класс TBitmap соответствует растровому (битовому) образу – цифровому представлению изображения.
На практике битовые образы используются для хранения высококачественных изображений (фотографий, видео). За хорошее качество приходится
платить объемом используемой памяти. Потребности в памяти возрастают
в прямой пропорции с размерами изображения и глубиной цвета. Другой недостаток растровых образов заключается в сложности качественного масштабирования картинки. Любое изменение размера битового образа требует
либо дублирования строк и столбцов, либо их удаления, что неблагоприятным образом сказывается на качестве изображения.
В Windows поддерживается два формата хранения растровых изображений:
аппаратно-зависимая битовая карта (Device Depended Bitmap, DDB) и аппаратно-независимая битовая карта (Device Independent Bitmap, DIB). Формат
инкапсулирован в классе TBitmap в свойстве:
property HandleType: TBitmapHandleType;
type TBitmapHandleType = (bmDIB, bmDDB);

Формат DDB применялся в первых версиях Windows (до 3.0) и был хорош
для маленьких картинок с небольшим количеством цветов. Недостаток аппаратно-зависимой битовой карты заключается в сложности вывода изображения на графических устройствах с иной цветовой организацией. На смену
DDB пришел формат DIB, который содержит цветовую таблицу, отражающую соответствие двоичного представления пикселов цветам RGB. Чаще
всего файлы в формате DIB имеют расширение .bmp, реже – .dib.

Растровое изображение – класс TBitmap

259

Черно-белый (монохромный) битовый образ требует всего 1 бит для хранения информации об одном пикселе. Для перехода в монохромный режим установите в true свойство:
property Monochrome: Boolean;

На рис. 10.11 изображен монохромный растровый рисунок в формате DIB
размерностью 16×6 пикселов. Эту картинку можно представить в виде рядов
битов (скан-линий).
property ScanLine[Row: Integer]: Pointer;

В скан-линии черному пикселу соответствует нулевое значение, белому пикселу – единица. Группы по 8 бит записываются в шестнадцатеричной системе счисления. В памяти строки битовой карты упорядочиваются снизу
вверх, и после заголовка файла хранится нижняя строка.
Хранение цветного изображения
несколько сложнее и требует дополнительных битов. Количество
цветов, представляемых в битовом
образе, соответствует 2 в степени
«число бит на пиксел»: 16 цветов –
0 1 0 1 0 1 0 1 0 0 0 0 0 0 0 1 = 55 01
4 бита на пиксел, 256 цветов – 8,
0 1 0 1 0 1 0 1 1 1 1 1 1 1 1 1 = 55 FF
High Color – 15 или 16, True Color –
0 1 0 1 0 1 0 1 0 0 0 0 0 0 0 1 = 55 01
24 бита на пиксел. Массив битов
0 1 0 1 0 1 0 1 1 1 1 1 1 1 1 1 = 55 FF
цветного растрового изображения
0 1 0 1 0 1 0 1 0 0 0 0 0 0 0 1 = 55 01
0 1 0 1 0 1 0 1 1 1 1 1 1 1 1 1 = 55 FF
(например, глубиной в 16 цветов)
выглядит следующим образом. КаРис. 10.11. Монохромный битовый образ
ждая скан-линия включает 4 цветовых плоскости в следующей последовательности: красная, зеленая, синяя и плоскость интенсивности.
property PixelFormat: TPixelFormat;
type TPixelFormat = (pfDevice, pf1bit, pf4bit, pf8bit, pf15bit, pf16bit, pf24bit,
pf32bit, pfCustom);

Формат pfCustom предназначен для реализации программистом собственных
графических объектов – потомков класса TBitmap.
Как видно из рис. 10.1, класс TBitmap построен на основе класса TGraphic
и унаследовал все объявленные в предке свойства и методы. TBitmap инкапсулирует битовый образ Windows (HBitmap), включая палитру (HPalette). Объект TBitmap обладает двумя дескрипторами (карты и цветовой палитры):
property Handle: HBitmap;
property Palette: HPalette;

Дескриптор карты используется для доступа к функциям GDI. Дескриптор
палитры предназначен для чтения или изменения цветовой палитры. Два
очень похожих метода возвращают дескрипторы карты и палитры.
function ReleaseHandle: HBitmap;
function ReleasePalette: HPalette;

260

Глава 10. Графическая подсистема

Эти методы используются для передачи дескриптора другому объекту или
процедуре. После этого битовая карта обнуляет дескриптор. Возможность
разделения дескриптора между объектами объясняется наличием механизма кэширования. Но как только вы вновь обратитесь к дескриптору битовой
карты, разделение одной картинки между несколькими объектами заканчивается. Объект TBitmap получит собственную копию дескриптора. Для этой
цели предназначены методы:
procedure Dormant;
procedure FreeImage;

Процедура Dormant() создает в памяти растровый рисунок в формате DIB,
уничтожая разделяемые дескрипторы битовой карты. Процедура FreeImage() освобождает дескриптор TBitmap для дальнейшего использования
и внесения изменений.
Помимо унаследованных от класса TGraphic возможностей загрузки изображения из файла, потока и буфера обмена, класс TBitmap поддерживает загрузку из файла ресурсов.
procedure LoadFromResourceID(Instance: THandle; ResID: Integer);
procedure LoadFromResourceName(Instance: THandle; const ResName: string);

Параметр Instance хранит идентификатор файла. Выбор битовой карты из
ресурса осуществляется либо при помощи параметра ResID (идентификатор
ресурса), либо параметра ResName (имя элемента ресурса). Несколько подробнее вопрос работы с ресурсами рассмотрен в разделе «Создание библиотеки
ресурсов» главы 19 «Динамически подключаемые библиотеки».
Отказавшись от использования цветовой палитры, можно значительно ускорить прорисовку изображения, но при этом качество картинки ухудшается:
property IgnorePalette: Boolean;

Для того чтобы сделать растровый рисунок прозрачным, нужно указать цвет
фона битовой карты:
property TransparentColor: TColor;

и установить в состояние tmFixed свойство:
property TransparentMode: TTransparentMode;
type TTransparentMode = (tmAuto, tmFixed);

Если свойство Transparent установлено в автоматический режим (tmAuto), то
за фоновый цвет битовой карты будет приниматься цвет ее первого пиксела.
Для использования битовой карты в качестве маски для других битовых
карт предусмотрено свойство:
property MaskHandle: HBitmap;

и методы Mask() и ReleaseMaskHandle(). Метод Mask() конвертирует битовую
карту в монохромную картинку, заменив TransparentColor на белый цвет,
а все остальные цвета на черный.

Метафайл – класс TMetafile

261

procedure Mask(TransparentColor: TColor);
function ReleaseMaskHandle: HBitmap;

Свойство MaskHandle содержит дескриптор, обеспечивающий доступ к функциям GDI, заменяя все небелые пикселы на черные. Белые пикселы превращаются в точки фона. Задача функции ReleaseMaskHandle() аналогична задачам рассмотренных ранее методов ReleaseHandle() и ReleasePalette().

Метафайл – класс TMetafile
В отличие от представителя растровой графики TBitmap, класс TMetafile состоит из набора записей, соответствующих вызовам графических функций.
Благодаря такой особенности пропадает необходимость хранить данные,
описывающие каждый пиксел. Функции, рисующие линии, кривые и геометрические фигуры с прорисовкой пикселов справятся самостоятельно.
Обычно рисунки, построенные на основе метафайлов, называют векторной
графикой и широко используют в программах инженерной графики.
Преимущества векторной графики над растровой не требуют особых комментариев: масштабирование без потери качества, малые размеры требуемой памяти и быстрота вывода.
В Windows широко применяется два формата метафайлов: стандартный
(WMF) и более новая версия – расширенный (EMF). В Delphi класс TMetafile
инкапсулирует расширенный метафайл. Вместе с тем для обеспечения совместимости с форматом WMF опубликовано свойство:
property Enhanced: Boolean;

Главные усовершенствования формата EMF заключаются в обширном информационном заголовке файла .EMF, в поддержке новых функций и структур данных, во внедрении нового формата буфера обмена.
Низкоуровневый доступ к функциям GDI реализуется через дескрипторы
метафайла и палитры:
property Handle: HMetafile;
property Palette: HPalette;

Целый набор свойств связан с геометрическими размерами метафайла. Экранные размеры в пикселах:
property Height: Integer;
property Width: Integer;

Размеры в сотых долях миллиметра (0,01 мм):
property MMHeight: Integer;
property MMWidth: Integer;

Количество точек на дюйм в координатной системе метафайла:
property Inch: Word;

Информацию о создателе и краткое описание метафайла можно получить
из свойств (только для чтения):

262

Глава 10. Графическая подсистема

property CreatedBy: string;
property Description: string;

Объект TMetafile предназначен для вывода изображения на графическом
устройстве. А для того чтобы нарисовать метафайл, необходимо использовать возможности класса TMetafileCanvas.

Класс TMetafileCanvas
Холст метафайла является потомком уже известного нам класса TCanvas. В сотрудничестве с TMetafile класс TMetafileCanvas способен решить задачу по созданию новых и редактированию существующих рисунков векторной графики. Перечень свойств и методов практически полностью соответствует возможностям родительского класса TCanvas, за исключением следующего.
У класса TMetafileCanvas переопределен конструктор:
constructor Create(AMetafile: TMetafile; ReferenceDevice: HDC);

Конструктор создает экземпляр холста метафайла и выделяет ему дескриптор. Параметр AMetafile соответствует объекту TMetafile; параметр ReferenceDevice – дескриптор графического устройства.
procedure NewMetafile;
var mtf:TMetaFile;
begin
try
mtf:=TMetaFile.Create;
with TMetaFileCanvas.Create(mtf,0) do
begin
Brush.Color := clRed;
Ellipse(0,0,100,100);
Brush.Color := clYellow;
Rectangle(25,25,50,50);
Destroy;
end;
Form1.canvas.draw(0,0,mtf);
mtf.savetofile('C:\Metafile.emf');
finally
mtf.free;
end;
end;

Процедура NewMetafile создает векторный рисунок из двух объектов: красного эллипса и желтого квадрата, затем сохраняет метафайл в формате EMF.
Еще одно дополнение класса TMetafileCanvas – конструктор, отвечающий за
внесение в метафайл справочной текстовой информации (вспомните свойства класса TMetafile: CreatedBy и Description):
constructor CreateWithComment(AMetafile: TMetafile; ReferenceDevice: HDC; const
CreatedBy, Description: string);

Класс TJPEGImage

263

Класс TJPEGImage
Класс TJPEGImage инкапсулирует возможности популярного в Интернете и
в домашней электронной фотографии формата сжатой растровой картинки
Windows, называемого JPEG (Joint Photographic Expert Group). Формат
JPEG был специально разработан для хранения 24-битных изображений
в файлах малого размера. Метод сжатия, используемый в JPEG-файлах, допускает некоторые потери качества изображения, причем, чем больше степень сжатия, тем больше необратимые ухудшения.
Для поддержки проектом Delphi сжатых картинок подключите к нему модуль Jpeg.

К особенностям класса TJPEGImage следует отнести: отсутствие у него холста
(хотя объект JPEG может нарисовать себя на холсте других объектов); класс
не обеспечивает попиксельного доступа к своему растровому изображению;
благодаря объекту TJPEGData допускает совместное использование своего дескриптора.
Объект JPEG обязан не только сжимать растровые изображения, но и отображать распакованные данные в формате DIB. Самый простой вариант взаимодействия между сжатой и обычной растровыми картинками осуществляется
при помощи метода Assign():
procedure Assign(Source: TPersistent);

В этом случае выполняется автоматическое конвертирование формата из
DIB в JPEG или наоборот. Например:
var JPG: TJPEGImage;

JPG.Assign(Image1.Picture.Bitmap);
JPG.SaveToFile('c:\Picture.jpg')

Для преобразования рисунка в формате JPEG в DIB предназначен метод:
procedure DIBNeeded;

Обычно процедура используется перед вызовом метода Draw() холста. Такой
подход ускоряет прорисовку картинки. Например:
var JPG: TJPEGImage;

JPG.DIBNeeded;
Form1.Canvas.Draw(0,0,JPG);

Обратное преобразование из DIB в JPEG осуществляется методом:
procedure JPEGNeeded;

Для сжатия обычного растрового изображения в формат JPEG прежде всего
надо установить качество сжатия. Для этой цели в классе TJPEGImage опубликовано свойство:
type TJPEGQualityRange = 1..100;
property CompressionQuality: TJPEGQualityRange;

264

Глава 10. Графическая подсистема

Чем больше значение CompressionQuality, тем меньше потерь качества изображения, но больше размер результирующего файла. С уменьшением этого
значения степень сжатия возрастает, но и ухудшается рисунок. По умолчанию значение качества сжатия равно 90.
Сжатие растровой картинки осуществляется методом Compress. Процедура
Compress() автоматически вызывается при сохранении объекта JPEG в файл
и поток.
procedure Compress;

Два взаимосвязанных свойства определяют способ вывода изображения на
экран: по мере распаковки (такой метод называют прогрессивной разверткой) или только по завершении распаковки.
property ProgressiveEncoding: Boolean;
property ProgressiveDisplay: Boolean;

Прогрессивная развертка картинки (ProgressiveDisplay = true) возможна
только при условии, что файл сохранен в режиме прогрессивного сжатия
(ProgressiveEncoding = true). При использовании прогрессивной развертки допустимо сглаживание изображения:
property Smoothing: Boolean;

Качество изображения зависит не только от степени сжатия растровой картинки. Ряд свойств класса определяет работу TJPEGImage при декомпрессии
и показе изображения. Критерий скорость распаковки/качество изображения задается в свойстве:
property Performance: TJPEGPerformance;
type TJPEGPerformance = (jpBestQuality, jpBestSpeed);

При выборе значения jpBestQuality на распаковку файла затрачивается несколько больше времени, но зато получается более качественная картинка.
Выбор в пользу jpBestSpeed дает обратный результат. На качество сохраняемого изображения Performance не влияет.
Критерий скорость вывода изображения/глубина цветов задается в свойстве:
property PixelFormat: TJPEGPixelFormat;
type TJPEGPixelFormat = (jf24Bit, jf8Bit);

При выборе значения jf24Bit вы получите полнокровное 24-битное изображение, но на его вывод затратите значительно больше времени, чем на 8-битное.
Наряду с уже известным нам методом TCanvas StretchDraw(), позволяющим
масштабировать графические объекты (потомки класса TGraphic), класс
TJPEGImage поддерживает свой, более эффективный (быстрый) способ масштабирования:
property Scale: TJPEGScale;
type TJPEGScale = (jsFullSize, jsHalf, jsQuarter, jsEighth);

В соответствии со значениями TJPEGScale вы получите изображение в соотношении 1:1, 1:2, 1:4 и 1:8 к размерам исходной картинки.

Универсальное хранилище изображений – класс TPicture

265

Универсальное хранилище изображений –
класс TPicture
Класс TPicture служит классическим примером инкапсуляции и полиморфизма. TPicture представляет собой универсальный контейнер, способный
содержать любой из рассмотренных ранее графических объектов Windows:
TIcon, TBitmap, TMetafile и TJPEGImage. Более того, TPicture превосходно справляется с решением вопросов по вызову методов, характерных для конкретного потомка TGraphic, упрощая работу программиста.
Доступ к графическому объекту реализуется при помощи свойства:
property Graphic: TGraphic;

В дополнение к этому (в зависимости от типа объекта) доступ осуществляется через свойства:
property Icon: TIcon;
property Bitmap: TBitmap;
property Metafile: TMetafile;

Методы, связанные с загрузкой / сохранением графического объекта, перед
выполнением анализируют тип графического объекта, а затем вызывают соответствующие этим объектам методы:
procedure LoadFromFile(const FileName: string);
procedure SaveToFile(const FileName: string);
procedure LoadFromClipboardFormat(aFormat: Word; aData: THandle; APalette: HPALETTE);
procedure SaveToClipboardFormat(var aFormat: Word; var aData: THandle; var
APalette: HPALETTE);

Здесь aFormat – константа, описывающая формат данных, находящихся в буфере обмена. Например, именованная константа CF_BITMAP укажет, что мы
планируем получить (передать) обычное растровое изображение. Параметр
aData – собственно указатель на данные, aPalette – характеристика палитры.
Более подробно о форматах буфера обмена рассказано в главе 22 «Обмен данными
между процессами».

При разработке новых графических классов на базе TGraphic следующие методы регистрируют новые форматы файла и буфера обмена:
type TGraphicClass = class of TGraphic;
class procedure RegisterFileFormatRes(const AExtension: string; ADescriptionResID:
Integer; AGraphicClass: TGraphicClass);
class procedure RegisterFileFormat(const AExtension, ADescription: string;
AGraphicClass: TGraphicClass);
class procedure RegisterClipboardFormat(AFormat: Word; AGraphicClass: TGraphicClass);

Обратная операция осуществляется при помощи метода:
class procedure UnregisterGraphicClass(AClass: TGraphicClass);

266

Глава 10. Графическая подсистема

Процедура отменяет все действия по регистрации нового графического класса, осуществленные при помощи методов RegisterFileFormat(), RegisterFileFormatRes() и RegisterClipboardFormat().

Графические компоненты VCL
В главе 6 «Невидимые классы» мы
уже познакомились с классом
TGraphicControl – базовым классом
графических элементов управления. На его основе построен ряд
компонентов, отвечающих за вывод графических изображений
(рис. 10.12). У всех компонентов
опубликовано свойство Canvas, обеспечивающее доступ к основным
графическим функциям GDI. Не
будет лишним напоминание о том,
что потомки класса TGraphicControl не являются оконными элементами управления и поэтому не
имеют оконного дескриптора.

TGraphicControl
TPaintBox
TShape
TBevel
TImage
TCustomLabel

TLabel

Рис. 10.12. Графические компоненты VCL

Область для рисования – компонент TPaintBox
Компонент TPaintBox представляет собой прямоугольную область, ограничивающую пространство, доступное для рисования.
Ключевым методом элемента управления считается процедура:
procedure Paint;

Обработчик события OnPaint() реагирует на сообщение Windows WM_PAINT
и инициализирует процесс перерисовки холста компонента.
procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
PaintBox1.Canvas.Brush.Color:=clBlack;
PaintBox1.Canvas.Rectangle(10,10,50,50);
end;

Все графические функции описываются внутри обработчика события OnPaint(). Такой подход гарантирует «полную сохранность» изображения при
перекрытии рабочей поверхности TPaintBox другими формами, при сворачивании формы и т. п.

Фигура – компонент TShape
Компонент TShape предназначен для построения простейших геометрических фигур. Тип фигуры (прямоугольник, квадрат, прямоугольник или
квадрат с закругленными углами, эллипс, круг) задается свойством:

Графические компоненты VCL

267

property Shape: TShapeType;
type TShapeType = (stRectangle, stSquare, stRoundRect, stRoundSquare, stEllipse,
stCircle);

Тип пера и заливка фигуры определяются соответственно свойствами:
property Pen: TPen;
property Brush: TBrush;

Рельефная панель – компонент TBevel
Компонент TBevel в большей степени полезен дизайнеру, чем программисту.
Благодаря TBevel формы проекта приобретают более эстетичный вид и …
в общем, на этом его особенности заканчиваются.
Внешний вид компонента задается свойством:
property Shape: TBevelShape;
type TBevelShape = (bsBox, bsFrame, bsTopLine, bsBottomLine, bsLeftLine, bsRightLine,
bsSpacer);

Бордюр компонента может быть утопленным или приподнятым, что достигается свойствами:
property Style: TBevelStyle;
type TBevelStyle = (bsLowered, bsRaised);

Изображение – компонент TImage
Компонент TImageпредназначен для отображения графического изображения. В нем инкапсулирован объект TPicture, способный содержать пиктограмму, битовую карту, метафайл или другой графический объект, заранее
определенный программистом. Доступ к свойствам и методам TPicture осуществляется при помощи свойства:
property Picture: TPicture;

Например, загрузка файла осуществляется следующим образом:
Image1.Picture.LoadFromFile('c:\bitmap.bmp');

Ряд свойств TImage нацелен на настройку способа вывода изображения на поверхности компонента. Центрирование изображения обеспечивает свойство:
property Center: Boolean;

Установив в true свойство Stretch, можно растянуть изображение на всю рабочую поверхность компонента TImage. Исключение составляют пиктограммы (TIcon) – они на такой подвиг неспособны.
property Stretch: Boolean;

Свойство, отвечающее за прозрачность, определяет, скрывает ли компонент
находящиеся под ним объекты:
property Transparent: Boolean;

268

Глава 10. Графическая подсистема

При работе с большими по размеру и сжатыми изображениями (например,
рисунками в формате JPEG) целесообразно устанавливать в true свойство:
property IncrementalDisplay: Boolean;

При IncrementalDisplay = true вывод изображения осуществляется прямо в
процессе загрузки файла, в противном случае – сразу по окончании загрузки.

Анимированное изображение – компонент TAnimate
Рассмотрение графических компонентов окажется неполным, если мы не
уделим внимание компоненту, способному демонстрировать полноценную
анимацию. В Windows основным анимационным форматом считается
аудио-видеоклип (Audio Video Interleaved, AVI). Файл в формате AVI представляет собой серию обычных растровых картинок-кадров, отображаемых
поочередно. Специально для демонстрации клипов AVI разработан класс
TAnimate. В отличие от рассмотренных в этой главе классов, он не имеет ничего общего с TGraphicControl и берет начало от TWinControl.
Выбор клипа осуществляется несколькими способами. Первый и самый простой – использовать ресурсы динамически подключаемой библиотеки
Shell32.dll. Этот файл входит в состав Windows и содержит 8 клипов. Для
подключения клипа свойству CommonAVI необходимо присвоить значение, отличное от aviNone:
property CommonAVI: TCommonAVI;
type TCommonAVI = (aviNone, aviFindFolder, aviFindFile, aviFindComputer,
aviCopyFiles, aviCopyFile, aviRecycleFile, aviEmptyRecycle, aviDeleteFile);

Второй способ заключается в выборе отдельного файла с расширением .avi
при помощи свойства:
property FileName: TFileName;

Третий способ подключения клипа связан с использованием отдельного модуля-ресурса, содержащего анимационную картинку. Для этого в процессе выполнения приложения передайте в дескриптор ResHandle указатель на ресурс:
property ResHandle: THandle;
type THandle = Integer;

После чего воспользуйтесь одним из свойств:
property ResID: Integer;
property ResName: string;

В ResID указывается уникальный идентификатор анимации. Если же анимационная картинка имеет собственное имя, то присвойте имя свойству ResName.
Справочную информацию о текущем клипе (такую как размеры кадра по
вертикали и горизонтали и общее количество кадров) соответственно предоставляют свойства:
property FrameHeight: Integer;
property FrameWidth: Integer;
property FrameCount: Integer;

269

Графические компоненты VCL

За автоматическую установку размеров элемента управления отвечает свойство:
property AutoSize: Boolean;

Готовность клипа к воспроизведению можно проверить по состоянию свойства:
property Open: Boolean;

В случае готовности к проигрыванию свойство принимает значение true
и элемент управления TAnimate приступает к отображению первого кадра
клипа. Перевод свойства в false выгружает клип из памяти.
Для запуска анимации достаточно присвоить значение true свойству:
property Active: Boolean;

При желании ограничить диапазон отображаемых кадров установите стартовый и конечный кадры (конечно, в пределах FrameCount):
property StartFrame: SmallInt;
property StopFrame: SmallInt;

Количество повторов клипа устанавливается свойством Repetitions. Для организации бесконечного проигрывания в цикле присвойте свойству значение 0.
property Repetitions: Integer;

Для синхронизации показа клипа с внутренним таймером Windows воспользуйтесь свойством:
property Timers: Boolean;

Если клип не содержит звука, то рекомендую оставлять свойство Timers с установками по умолчанию (false), т. к. в этом случае для клипа создается отдельный
поток, практически не влияющий на производительность системы.

Методы класса TAnimate представлены в табл. 10.5.
Таблица 10.5. Методы класса TAnimate
Метод

Описание

function
CanAutoSize(var New- Отвечает за установку размеров элемента управлеWidth, NewHeight: Integer): Boo- ния в соответствии с размерами кадров клипа. Вызывается автоматически при установке свойства
lean;
AutoSize в true.
procedure Play(FromFrame, To- Проигрывает клип Count раз, начиная с кадра FromFrame: Word; Count: Integer);
Frame до ToFrame.
procedure Stop;

Останавливает проигрывание клипа по аналогии
с установкой в false свойства Active.

procedure Reset;

Очищает значения, заданные в свойствах StartFrame и StopFrame, и позиционирует клип на первом
кадре. Последовательно генерирует события OnClose() и OnOpen().

procedure Seek(Frame: SmallInt); Отображает кадр с порядковым номером Frame.

270

Глава 10. Графическая подсистема

События, генерируемые элементом управления TAnimate, представлены
в табл. 10.6.
Таблица 10.6. События класса TAnimate
Событие

Описание

property OnOpen: TNotifyEvent;; Возникает сразу после открытия клипа (Open:= true).
property OnStart: TNotifyEvent; Возникает в момент воспроизведения клипа (Active:=true или вызов метода Play).
property OnStop: TNotifyEvent; Возникает после прекращения воспроизведения
клипа.
property OnClose: TNotifyEvent; Возникает при закрытии клипа (Open:= false).

Метка – компонент TLabel
Метка – один из наиболее часто используемых графических компонентов.
Этот элемент управления предназначен для вывода на экран поясняющего
текста. Ключевое свойство компонента TLabel – его заголовок Caption. Это
отображаемая меткой текстовая строка. Она недоступна для редактирования пользователем и может изменяться только из кода программы.
property Caption : string;

За автоподстройку геометрических размеров компонента при отображении
текста отвечают два свойства:
property AutoSize: Boolean;
property WordWrap: Boolean;

//по умолчанию true
//по умолчанию false

По умолчанию (AutoSize=true и WordWrap=false) текст в метке располагается
в одну строку и метка растягивается по горизонтали так, чтобы вместить
весь текст из свойства Caption. Размер компонента по вертикали будет зависеть только от высоты установленного шрифта. Если оба свойства переведены в true, компонент зафиксирует свой горизонтальный размер, но позволит
изменять размер по вертикали для вывода текста в несколько строк.
Для включения прозрачности установите в true свойство:
property Transparent: Boolean; //по умолчанию true

С меткой связаны два специфичных события:
property OnMouseEnter: TNotifyEvent;
property OnMouseLeave: TNotifyEvent;

Первое событие генерируется в момент появления над меткой курсора мыши,
второе вызывается при уходе курсора от клиентской области компонента.

Работа с графикой методами Win32 API
Как правило, непосредственное использование функций GDI требуется при
рисовании чего-то такого, что недостижимо с помощью методов визуальной

Работа с графикой методами Win32 API

271

библиотеки компонентов Delphi, и в частности ключевого графического
класса TCanvas. В таком случае надо прежде всего позаботиться о получении
дескриптора контекста графического устройства, на поверхности которого
мы планируем рисовать.
Контекст устройства (DC, device context) – это структура размером 64 Кбайт,
автоматически создаваемая Windows и умеющая осуществлять вывод на
устройство. Воспринимайте контекст устройства как связующую нить между программным кодом и областью рисования. Вместо непосредственного
направления графической информации на аппаратное устройство (экран,
принтер) приложение передает ее в контекст, и только затем операционная
система переадресует вывод на «железо», точнее отдаст соответствующую
команду драйверу устройства.
Благодаря контексту устройства процесс рисования становится аппаратнонезависимым, и мы можем использовать одинаковые графические процедуры для любого устройства вывода, будь то экран монитора, принтер или
плоттер. Запросив у Windows дескриптор контекста устройства, вы уведомляете систему о своих намерениях использовать графическое устройство.
function CreateDC(lpDriver, lpDevice, lpOutput : PChar; const DEVMODE :
Pointer) : hDC;

Здесь lpDriver – указатель на строку с именем файла драйвера устройства,
например DISPLAY для получения дескриптора экрана; lpDevice – имя конкретного используемого устройства, например Epson LQ-100; параметр
lpOutput игнорируется и всегда должен быть пуст (nil); DEVMODE – указатель на
структуру, содержащую данные инициализации для драйвера устройства.
Для применения параметров по умолчанию устанавливайте DEVMODE в nil.
В нижеприведенном коде дескриптор дисплея возвращается функцией CreateDC(), а удаляется DeleteDC() или ReleaseDC().
var DC:hDC;
pS:PChar;
begin
DC:=CreateDC('DISPLAY', nil, nil, nil); // получение дескриптора
pS:=PChar('Пишем на экране');
TextOut(DC,10,10,pS,StrLen(pS));
DeleteDC(DC);
// удаление дескриптора
end;

Благодаря дескриптору дисплея мы получили возможность выводить текст
непосредственно на экране, а не в пределах, ограниченных рабочей формой
приложения.
Для получения дескриптора клиентской области окна используется другая
функция Windows:
function GetDC(HWND : hWnd) : hDC;

Метод требует передачи всего одного параметра – дескриптора окна, для которого должен быть получен контекст устройства. Например:

272

Глава 10. Графическая подсистема

DC:=GetDC(Form1.Handle);

Для того чтобы получить контекст поверхности для рисования формы, достаточно воспользоваться свойством Handle холста этой формы:
DC:=Form1.Canvas.Handle;

Еще одна полезная функция GDI обеспечит вас подробной информацией
о параметрах графического устройства:
function GetDeviceCaps( DC : hdc, nIndex : integer) : integer;

В качестве параметров функции выступают уже знакомый нам дескриптор
DC и идентификатор nIndex. Идентификатор может принимать значения ряда
констант, объявленных Windows, например: HORZSIZE – ширина физического
экрана в миллиметрах, VERTSIZE – высота в миллиметрах, HORZRES – ширина
экрана в пикселах, VERTRES – высота в пикселах и т. д.
procedure TForm1.FormResize(Sender: TObject);
var MWidth, MHeight:integer;
begin
MHeight:=GetDeviceCaps(Canvas.Handle,VERTRES)-100;
MWidth:=GetDeviceCaps(Canvas.Handle,HORZRES)-100;
if Form1.Height>MHeight then Form1.Height:=MHeight;
if Form1.Width>MWidth then Form1.Width:=MWidth;
end;

В приведенном примере вертикальный и горизонтальный размеры формы не
могут превышать соответственно высоту и ширину экрана монитора за вычетом 100 пикселов.

Обновление области
В арсенале Windows GDI имеется полезная функция, указывающая системе
о необходимости перерисовать некоторую прямоугольную область:
function InvalidateRect(hWnd : HWND ; lpRect : pRect; Erase : Boolean) :
Boolean;

Здесь hWnd – дескриптор окна, чья поверхность будет обновлена; lpRect – указатель на структуру TRect, хранящую координаты прямоугольника; Erase –
параметр, определяющий, есть ли необходимость в стирании прямоугольника перед его перерисовкой (true).
var rct : TRect;
begin
rct:=Rect(10,10,100,100);
INVALIDATERECT(Form1.Handle,@rct,true);
end;

Схожую задачу решает функция Windows GDI под названием InvalidateRgn(). В отличие от своей коллеги, обновляющей прямоугольную область, эта функция инициирует перерисовку региона.

Работа с графикой методами Win32 API

273

Координатные системы
ОС Windows для определения областей на поверхности рисования использует три системы координат:
• координаты устройства;



логические координаты;
мировые координаты.

Координаты устройства определяются техническими характеристиками аппаратного устройства, с которым работает Windows. В качестве единицы измерения применяется пиксел, а ориентация координат – слева направо для
оси X и сверху вниз для оси Y. Начало отсчета координат расположено
в верхнем левом углу.
Логические координаты соответствуют координатам любой области, обладающей контекстом устройства. Например, контекстом обладает поверхность формы TForm и поверхность холста TPaintBox.
Мировые координаты предназначены для осуществления операций вращения растра, сдвига, скручивания и т. п.
Методы GDI требуют передачи логических координат. Получив эти значения, операционная система транслирует их в пикселы – координаты устройства. Для получения полной картины стоит упомянуть еще ряд терминов,
связанных с координатными системами. Когда мы работаем с экраном в целом, говорят, что работа осуществляется в экранных координатах.
При работе с окном применяют еще два термина: координаты рабочей области окна и полные координаты окна. Полные координаты окна определяют окно программы целиком, включая заголовок, меню, полосы прокрутки
и рамку окна. Координаты рабочей области окна описывают только внутреннюю поверхность окна.
Все три системы координат отличаются физическим расположением точки
начала отсчета координат (0,0). Для экранных координат это левая верхняя
точка экрана. Для координат рабочей области окна это левая верхняя точка
рабочей области окна. Для полных координат окна это левый верхний угол
рамки окна.

Режимы отображения
Практически все функции Windows GDI и как следствие методы класса
TCanvas в качестве параметров требуют передачи логических координат. Вызывая метод TextOut(5, 5, 'Hello Word!'), мы информируем систему вывода
о том, что хотим увидеть надпись «Hello World!» в левом верхнем углу, причем верхний левый пиксел текста должен находиться в точке с координатами (5,5). Логические координаты созданы специально для нас с целью максимально упростить нашу работу.
Операционная система должна преобразовать логические координаты в физические координаты устройства – единицы измерения устройства, на кото-

274

Глава 10. Графическая подсистема

ром мы рисуем. Мы уже привыкли к тому, что система координат Windows
берет начало в левом верхнем углу рабочей поверхности в точке с координатами (0,0), горизонтальная ось X проведена из крайней верхней точки поверхности рисования в крайнюю правую точку, а ось Y берет начало в той же
точке (0,0) и идет вертикально вниз.
Такой режим отображения называется текстовым (в терминах Windows ему
соответствует идентификатор MM_TEXT), поскольку оси X и Y проведены точно
так же, как и при чтении текста – слева направо, сверху вниз. Как видите,
в данном случае физические координаты устройства (пикселы) полностью
совпадают с нашими логическими координатами. Однако в Windows помимо
текстового режима реализовано еще семь режимов отображения (табл. 10.7).
Таблица 10.7. Режимы отображения в Windows
Классификация

Режим
Логические
отображения единицы

Направления осей

Текстовый режим MM_TEXT

Пиксел

X – вправо, Y – вниз

Метрические ре- MM_LOMETRIC
жимы
MM_HIMETRIC

0.1 мм

X – вправо, Y – вверх

Пользовательские режимы

0.01 мм

MM_LOENGLISH

0.01 дюйма (0.254 мм)

MM_HIENGLISH

0.001 дюйма (0.0254 мм)

MM_TWIPS

1/1440 дюйма (0.0176 мм)

MM_ISOTROPIC

Настраиваемые

Настраивается

MM_ANISOTROPIC Настраиваемые

Настраивается

В первую очередь режимы отображения различаются единицами измерения. Если не брать во внимание настраиваемые пользовательские режимы
отображения, то самый точный из режимов – MM_HIMETRIC. Он позволяет осуществлять операции рисования с шагом в одну сотую миллиметра. Самый
грубый – MM_LOENGLISH; его единица измерения – одна сотая дюйма. Второе
различие между режимами заключается в направлении вертикальной оси Y.
По умолчанию всегда установлен текстовый режим MM_TEXT. Особенность текстового режима заключается в том, что его логическая единица измерения полностью
совпадает с физической; это пиксел. Геометрический размер пиксела определяется
качеством монитора, например у моего экрана он составляет 0,2 мм.

За назначение режима отображения отвечает функция Windows:
function SetMapMode(DC : HDC; fnMapMode : Integer) : Integer;

где DC – дескриптор контекста устройства, а fnMapMode – константа режима
отображения. В случае успешного выполнения метод возвращает значение
предыдущего режима отображения, в противном случае – 0.
Для определения текущего режима понадобится метод из коллекции Windows API:

275

Работа с графикой методами Win32 API

function GetMapMode(DC : HDC) : Integer;

Функция требует указать дескриптор контекста DC и возвращает соответствующий режим отображения. Переключение между режимами отображения элементарно и представлено в следующем листинге.
var DC: HDC;
s : string;
begin
DC:=Form1.Canvas.Handle;
SetMapMode(DC,MM_TEXT);
s:='режим MM_Text';
TextOut(DC,5,5,PChar(s),Length(s));

//получаем контекст поверхности формы
//текстовый режим отображения
//строка начинается в точке (5,5) пикселов

SetMapMode(DC,MM_LOMETRIC);
//переводим в метрический режим
s:='режим MM_LOMETRIC';
TextOut(DC,100,-100,PChar(s),Length(s)); //строка начинается в точке (10,-10) мм
end;

Поясню некоторые строки листинга. Во-первых, для вывода текста применялся метод TextOut() из набора функций Windows GDI, а не изученный нами ранее одноименный метод класса TCanvas. Однако никто не запрещает
вместо методов Windows API применять методы из визуальной библиотеки
компонентов Delphi (VCL), и в частности методы холста формы. Только
в этом случае вместо непосредственного вызова TextOut() требуется указать,
что мы планируем работать с методом холста:
Form1.Canvas.TextOut(5,5,s);

Во вторых, обращаю внимание, что в режиме отображения MM_LOMETRIC
(табл. 10.7) направление оси Y отличается от направления этой же оси в текстовом режиме. Во всех метрических режимах отображения ось Y направлена снизу вверх. А так как начало отсчета координат по умолчанию стартует
в левом верхнем углу, то при повторном выводе текста мы были вынуждены
указать отрицательную координату Y; в противном случае Windows вывела
бы текст за пределами области вывода.

Изменение начала координат
Операционная система позволяет изменять местоположение точки начала
отсчета координат области вывода и окна. Для этого предусмотрены две
функции Win32 API. Для установки начала координат области просмотра
контекста устройства DC предназначен метод:
function SetViewportOrgEx(DC : HDC; x, y : Integer; Point : PPoint) :
Boolean;

где (x, y) – координаты нового начала координат области просмотра в единицах измерения устройства, а Point – указатель на запись типа TPoint; в эту
запись будет сохранено предыдущее значение начала координат. Если эти
данные не нужны, синтаксис функции вместо указателя на структуру до-

276

Глава 10. Графическая подсистема

пускает передачу пустого указателя nil. В случае успеха функция порадует
нас результатом true. Работа метода поясняется в следующем листинге:
procedure TForm1.FormPaint(Sender: TObject);
var DC:hDC;
begin
DC:=Form1.Canvas.Handle;
SetViewportOrgEx(DC,
Form1.ClientWidth div 2, //точка x - середина клиентской области
Form1.ClientHeight div 2, //точка y - середина клиентской области
nil);
MoveToEx(DC,-100,0,nil); LineTo(DC,100,0); //рисуем