• Название:

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

  • Размер: 11.45 Мб
  • Формат: PDF
  • или

    По договору между издательством «Символ-Плюс» и Интернет-магазином «Books.Ru – Книги России» единственный легальный способ
    получения данного файла с книгой ISBN 5-93286-074-X, название
    «Delphi. Профессиональное программирование» – покупка в Интернет-магазине «Books.Ru – Книги России». Если Вы получили данный
    файл каким-либо другим образом, Вы нарушили международное законодательство и законодательство Российской Федерации об охране авторского права. Вам необходимо удалить данный файл, а также сообщить издательству «Символ-Плюс» (piracy@symbol.ru), где именно
    Вы получили данный файл.

    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
    Совместно с данными перечислимого типа зачастую используют следующие
    функции:
    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.

    Третий пример добавляет десять строк в конец текстового файла. Он также
    не должен вызвать особых затруднений, только не забудьте перед запуском
    программы создать в корне диска файл 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 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); //рисуем ось x из точки -100 в 100
    MoveToEx(DC,0,-100,nil); LineTo(DC,0,100); //рисуем ось y из точки -100 в 100
    TextOut(DC,1,1,PChar('0,0'),3);
    //подпись начала координат
    end;

    В примере начало координат переносится в точку, находящуюся точно посередине клиентской области формы. Координаты новой точки выясняются
    при помощи элементарных операций деления ширины и высоты клиентской области формы на 2. После переноса начала координат в новое место
    мы рисуем две линии, соответствующие новым координатным осям X и Y.
    Затем чуть ниже точки начала координат выводим подпись «0,0». Результаты нашей деятельности наглядно представлены на рис. 10.13.
    Точно такой же результат может быть получен с помощью функции SetWindowOrgEx():
    function SetWindowOrgEx(DC : HDC; x, y : Integer; Point : PPoint) : Boolean;

    Основное отличие этого метода от рассмотренного ранее SetViewportOrgEx()
    состоит в том, что здесь аргументы (x, y) задаются в логических координатах. Таким образом, метод позволяет манипулировать удобными единицами
    измерения. В следующем листинге за единицу измерения принята 0,1 мм:
    procedure TForm1.FormPaint(Sender: TObject);
    var DC:hDC;
    begin
    DC:=Form1.Canvas.Handle;
    SetMapMode(DC,MM_LOMETRIC);
    //шаг режима 0.1 мм
    SetViewportOrgEx(DC, 10, 10, Nil);
    //перенос точки
    MoveToEx(DC,-90,0,Nil); LineTo(DC,1000,0);
    MoveToEx(DC,0,-1000,Nil); LineTo(DC,0,90);
    TextOut(DC,5,-5,PChar('0,0'),3);
    end;

    Теперь начало координат перенесено в точку, отстоящую на 10 мм от верхнего левого угла клиентской области формы (рис. 10.14).
    Настала очередь рассказать о функциях, информирующих программиста
    о положении начала координат:
    function GetViewportOrgEx(DC : HDC; Point : PPoint) : Boolean;
    function GetWindowOrgEx(DC : HDC; Point : PPoint) : Boolean;

    277

    Работа с графикой методами Win32 API

    Рис. 10.13. Начало координат в центре
    клиентской области формы

    Рис. 10.14. Начало координат в 10 мм
    от верхнего левого угла формы

    Оба метода из контекста DC берут информацию о координатах начала области
    вывода и помещают данные в структуру типа TPoint. Первый метод возвращает значение в координатах устройства, второй – в логических координатах.

    Формула преобразования координат
    Разбавим материал по программированию двумя простейшими математическими выражениями, с помощью которых операционная система осуществляет преобразование логических единиц измерения, установленных в текущем
    режиме отображения, в физические координаты (координаты устройства).
    VE
    WE x

    Для координаты X:

    x
    D x = VOx + ( x – WOx ) ⋅ ------------.

    Для координаты Y:

    y
    -- .
    D y = VOy + ( x – WOy ) ⋅ -----------

    VE
    WE y

    Так как формулы для координат X и Y идентичны, прокомментируем только первое выражение, где:
    D x – результат вычисления, физическая координата X аппаратного
    устройства;
    VO x – начало координат области вывода в координатах устройства;
    x
    – преобразуемая логическая координата текущего режима отображения;
    WO x – начало координат окна в логических координатах;
    VE x – протяженность области вывода в координатах устройства;
    WE x – протяженность окна в логических координатах;